frontmcp 1.2.1 → 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.
Files changed (108) hide show
  1. package/package.json +4 -4
  2. package/src/commands/build/exec/bin-meta.d.ts +49 -0
  3. package/src/commands/build/exec/bin-meta.js +68 -0
  4. package/src/commands/build/exec/bin-meta.js.map +1 -0
  5. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js +195 -3
  6. package/src/commands/build/exec/cli-runtime/generate-cli-entry.js.map +1 -1
  7. package/src/commands/build/exec/cli-runtime/plugin-emitter.d.ts +160 -0
  8. package/src/commands/build/exec/cli-runtime/plugin-emitter.js +512 -0
  9. package/src/commands/build/exec/cli-runtime/plugin-emitter.js.map +1 -0
  10. package/src/commands/build/exec/cli-runtime/schema-extractor.d.ts +13 -1
  11. package/src/commands/build/exec/cli-runtime/schema-extractor.js +29 -3
  12. package/src/commands/build/exec/cli-runtime/schema-extractor.js.map +1 -1
  13. package/src/commands/build/exec/cli-runtime/skill-md-compose.d.ts +25 -0
  14. package/src/commands/build/exec/cli-runtime/skill-md-compose.js +63 -0
  15. package/src/commands/build/exec/cli-runtime/skill-md-compose.js.map +1 -0
  16. package/src/commands/build/exec/index.js +26 -0
  17. package/src/commands/build/exec/index.js.map +1 -1
  18. package/src/commands/dev/bridge/child-supervisor.d.ts +48 -0
  19. package/src/commands/dev/bridge/child-supervisor.js +228 -0
  20. package/src/commands/dev/bridge/child-supervisor.js.map +1 -0
  21. package/src/commands/dev/bridge/errors.d.ts +23 -0
  22. package/src/commands/dev/bridge/errors.js +34 -0
  23. package/src/commands/dev/bridge/errors.js.map +1 -0
  24. package/src/commands/dev/bridge/index.d.ts +30 -0
  25. package/src/commands/dev/bridge/index.js +220 -0
  26. package/src/commands/dev/bridge/index.js.map +1 -0
  27. package/src/commands/dev/bridge/log.d.ts +29 -0
  28. package/src/commands/dev/bridge/log.js +82 -0
  29. package/src/commands/dev/bridge/log.js.map +1 -0
  30. package/src/commands/dev/bridge/state-machine.d.ts +56 -0
  31. package/src/commands/dev/bridge/state-machine.js +245 -0
  32. package/src/commands/dev/bridge/state-machine.js.map +1 -0
  33. package/src/commands/dev/bridge/stdio-framer.d.ts +47 -0
  34. package/src/commands/dev/bridge/stdio-framer.js +128 -0
  35. package/src/commands/dev/bridge/stdio-framer.js.map +1 -0
  36. package/src/commands/dev/bridge/upstream-client.d.ts +49 -0
  37. package/src/commands/dev/bridge/upstream-client.js +159 -0
  38. package/src/commands/dev/bridge/upstream-client.js.map +1 -0
  39. package/src/commands/dev/bridge/watcher.d.ts +30 -0
  40. package/src/commands/dev/bridge/watcher.js +87 -0
  41. package/src/commands/dev/bridge/watcher.js.map +1 -0
  42. package/src/commands/dev/dev.d.ts +18 -1
  43. package/src/commands/dev/dev.js +134 -14
  44. package/src/commands/dev/dev.js.map +1 -1
  45. package/src/commands/dev/inspector.d.ts +13 -1
  46. package/src/commands/dev/inspector.js +77 -3
  47. package/src/commands/dev/inspector.js.map +1 -1
  48. package/src/commands/dev/port.d.ts +23 -0
  49. package/src/commands/dev/port.js +87 -0
  50. package/src/commands/dev/port.js.map +1 -0
  51. package/src/commands/dev/register.d.ts +1 -1
  52. package/src/commands/dev/register.js +28 -4
  53. package/src/commands/dev/register.js.map +1 -1
  54. package/src/commands/dev/test.d.ts +26 -1
  55. package/src/commands/dev/test.js +181 -64
  56. package/src/commands/dev/test.js.map +1 -1
  57. package/src/commands/eject/mcp-client.d.ts +25 -0
  58. package/src/commands/eject/mcp-client.js +74 -0
  59. package/src/commands/eject/mcp-client.js.map +1 -0
  60. package/src/commands/eject/register.d.ts +9 -0
  61. package/src/commands/eject/register.js +56 -0
  62. package/src/commands/eject/register.js.map +1 -0
  63. package/src/commands/install/install-claude-plugin.d.ts +13 -0
  64. package/src/commands/install/install-claude-plugin.js +327 -0
  65. package/src/commands/install/install-claude-plugin.js.map +1 -0
  66. package/src/commands/install/register.d.ts +16 -0
  67. package/src/commands/install/register.js +70 -0
  68. package/src/commands/install/register.js.map +1 -0
  69. package/src/commands/scaffold/create.js +44 -0
  70. package/src/commands/scaffold/create.js.map +1 -1
  71. package/src/commands/skills/from-entry.d.ts +31 -0
  72. package/src/commands/skills/from-entry.js +68 -0
  73. package/src/commands/skills/from-entry.js.map +1 -0
  74. package/src/commands/skills/install.d.ts +12 -0
  75. package/src/commands/skills/install.js +173 -8
  76. package/src/commands/skills/install.js.map +1 -1
  77. package/src/commands/skills/register.js +7 -3
  78. package/src/commands/skills/register.js.map +1 -1
  79. package/src/config/frontmcp-config.loader.d.ts +28 -0
  80. package/src/config/frontmcp-config.loader.js +146 -67
  81. package/src/config/frontmcp-config.loader.js.map +1 -1
  82. package/src/config/frontmcp-config.resolve.d.ts +67 -0
  83. package/src/config/frontmcp-config.resolve.js +118 -0
  84. package/src/config/frontmcp-config.resolve.js.map +1 -0
  85. package/src/config/frontmcp-config.schema.d.ts +207 -0
  86. package/src/config/frontmcp-config.schema.js +217 -1
  87. package/src/config/frontmcp-config.schema.js.map +1 -1
  88. package/src/config/frontmcp-config.types.d.ts +133 -0
  89. package/src/config/frontmcp-config.types.js.map +1 -1
  90. package/src/config/index.d.ts +2 -1
  91. package/src/config/index.js +3 -1
  92. package/src/config/index.js.map +1 -1
  93. package/src/core/args.d.ts +13 -0
  94. package/src/core/args.js.map +1 -1
  95. package/src/core/bridge.js +39 -0
  96. package/src/core/bridge.js.map +1 -1
  97. package/src/core/cli.d.ts +0 -6
  98. package/src/core/cli.js +23 -3
  99. package/src/core/cli.js.map +1 -1
  100. package/src/core/help.d.ts +1 -1
  101. package/src/core/help.js +27 -6
  102. package/src/core/help.js.map +1 -1
  103. package/src/core/program.d.ts +1 -1
  104. package/src/core/program.js +56 -12
  105. package/src/core/program.js.map +1 -1
  106. package/src/core/project-commands.d.ts +44 -0
  107. package/src/core/project-commands.js +216 -0
  108. package/src/core/project-commands.js.map +1 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "frontmcp",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "FrontMCP command line interface",
5
5
  "author": "AgentFront <info@agentfront.dev>",
6
6
  "homepage": "https://docs.agentfront.dev",
@@ -31,9 +31,9 @@
31
31
  },
32
32
  "dependencies": {
33
33
  "@clack/prompts": "^0.10.0",
34
- "@frontmcp/lazy-zod": "1.2.1",
35
- "@frontmcp/utils": "1.2.1",
36
- "@frontmcp/skills": "1.2.1",
34
+ "@frontmcp/lazy-zod": "1.3.0",
35
+ "@frontmcp/utils": "1.3.0",
36
+ "@frontmcp/skills": "1.3.0",
37
37
  "commander": "^13.0.0",
38
38
  "tslib": "^2.3.0",
39
39
  "vectoriadb": "^2.2.0",
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Issue #411 — write `bin-meta.json` next to the CLI bundle. The per-bin
3
+ * `<bin> install -p claude|codex` reads this sidecar at runtime to drive
4
+ * the shared plugin-emitter without re-running schema extraction.
5
+ *
6
+ * Shape:
7
+ * {
8
+ * "name": "<bin>",
9
+ * "version": "<bin-version>",
10
+ * "description": "<from cliConfig or package.json>",
11
+ * "mcpDefault": { "command": "<bin>", "args": ["serve", "--stdio"] },
12
+ * "prompts": [{ "name", "description", "arguments": [...] }],
13
+ * "skills": [{ "name", "description", "instructionFile" (path under _skills/), "resourceDirs": { references?, examples?, scripts?, assets? } }]
14
+ * }
15
+ */
16
+ import type { FrontmcpExecConfig } from './config';
17
+ import type { ExtractedSchema } from './cli-runtime/schema-extractor';
18
+ export interface BinMeta {
19
+ name: string;
20
+ version: string;
21
+ description: string;
22
+ mcpDefault: {
23
+ command: string;
24
+ args: string[];
25
+ };
26
+ prompts: Array<{
27
+ name: string;
28
+ description?: string;
29
+ arguments?: Array<{
30
+ name: string;
31
+ description?: string;
32
+ required?: boolean;
33
+ }>;
34
+ }>;
35
+ skills: Array<{
36
+ name: string;
37
+ description?: string;
38
+ tags?: string[];
39
+ license?: string;
40
+ instructionFile?: string;
41
+ resourceDirs?: {
42
+ references?: string;
43
+ examples?: string;
44
+ scripts?: string;
45
+ assets?: string;
46
+ };
47
+ }>;
48
+ }
49
+ export declare function writeBinMeta(outDir: string, config: FrontmcpExecConfig, schema: ExtractedSchema): Promise<void>;
@@ -0,0 +1,68 @@
1
+ "use strict";
2
+ /**
3
+ * Issue #411 — write `bin-meta.json` next to the CLI bundle. The per-bin
4
+ * `<bin> install -p claude|codex` reads this sidecar at runtime to drive
5
+ * the shared plugin-emitter without re-running schema extraction.
6
+ *
7
+ * Shape:
8
+ * {
9
+ * "name": "<bin>",
10
+ * "version": "<bin-version>",
11
+ * "description": "<from cliConfig or package.json>",
12
+ * "mcpDefault": { "command": "<bin>", "args": ["serve", "--stdio"] },
13
+ * "prompts": [{ "name", "description", "arguments": [...] }],
14
+ * "skills": [{ "name", "description", "instructionFile" (path under _skills/), "resourceDirs": { references?, examples?, scripts?, assets? } }]
15
+ * }
16
+ */
17
+ Object.defineProperty(exports, "__esModule", { value: true });
18
+ exports.writeBinMeta = writeBinMeta;
19
+ const tslib_1 = require("tslib");
20
+ const path = tslib_1.__importStar(require("path"));
21
+ const utils_1 = require("@frontmcp/utils");
22
+ async function writeBinMeta(outDir, config, schema) {
23
+ const meta = {
24
+ name: config.name,
25
+ version: config.version ?? '0.0.0',
26
+ description: config.cli?.description ?? `${config.name} (FrontMCP server)`,
27
+ mcpDefault: { command: config.name, args: ['serve', '--stdio'] },
28
+ prompts: schema.prompts.map((p) => ({
29
+ name: p.name,
30
+ description: p.description,
31
+ arguments: p.arguments,
32
+ })),
33
+ skills: schema.skillAssets.map((asset) => {
34
+ // The bin runtime expects RELATIVE paths under `_skills/` so the bundle
35
+ // is portable across install destinations.
36
+ const instructionFile = asset.instructionFile
37
+ ? path.join('_skills', `${asset.skillName}--${path.basename(asset.instructionFile)}`)
38
+ : undefined;
39
+ const resourceDirs = {};
40
+ for (const kind of ['references', 'examples', 'scripts', 'assets']) {
41
+ if (asset.resourceDirs?.[kind]) {
42
+ resourceDirs[kind] = path.join('_skills', `${asset.skillName}--${kind}`);
43
+ }
44
+ }
45
+ return {
46
+ name: asset.skillName,
47
+ description: asset.description,
48
+ tags: asset.tags && asset.tags.length > 0 ? asset.tags : undefined,
49
+ license: asset.license,
50
+ instructionFile,
51
+ resourceDirs: Object.keys(resourceDirs).length > 0 ? resourceDirs : undefined,
52
+ };
53
+ }),
54
+ };
55
+ // Drop undefined fields so the JSON payload matches the TS interface (no
56
+ // explicit "description": undefined keys land in stringified output).
57
+ const cleanSkills = meta.skills.map((s) => omitUndefined(s));
58
+ await (0, utils_1.writeJSON)(path.join(outDir, 'bin-meta.json'), { ...meta, skills: cleanSkills });
59
+ }
60
+ function omitUndefined(obj) {
61
+ const out = {};
62
+ for (const [k, v] of Object.entries(obj)) {
63
+ if (v !== undefined)
64
+ out[k] = v;
65
+ }
66
+ return out;
67
+ }
68
+ //# sourceMappingURL=bin-meta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin-meta.js","sourceRoot":"","sources":["../../../../../src/commands/build/exec/bin-meta.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;;;;GAcG;;AA6BH,oCA0CC;;AArED,mDAA6B;AAE7B,2CAA4C;AAyBrC,KAAK,UAAU,YAAY,CAChC,MAAc,EACd,MAA0B,EAC1B,MAAuB;IAEvB,MAAM,IAAI,GAAY;QACpB,IAAI,EAAE,MAAM,CAAC,IAAI;QACjB,OAAO,EAAE,MAAM,CAAC,OAAO,IAAI,OAAO;QAClC,WAAW,EAAE,MAAM,CAAC,GAAG,EAAE,WAAW,IAAI,GAAG,MAAM,CAAC,IAAI,oBAAoB;QAC1E,UAAU,EAAE,EAAE,OAAO,EAAE,MAAM,CAAC,IAAI,EAAE,IAAI,EAAE,CAAC,OAAO,EAAE,SAAS,CAAC,EAAE;QAChE,OAAO,EAAE,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;YAClC,IAAI,EAAE,CAAC,CAAC,IAAI;YACZ,WAAW,EAAE,CAAC,CAAC,WAAW;YAC1B,SAAS,EAAE,CAAC,CAAC,SAAS;SACvB,CAAC,CAAC;QACH,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE;YACvC,wEAAwE;YACxE,2CAA2C;YAC3C,MAAM,eAAe,GAAG,KAAK,CAAC,eAAe;gBAC3C,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,SAAS,KAAK,IAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,eAAe,CAAC,EAAE,CAAC;gBACrF,CAAC,CAAC,SAAS,CAAC;YACd,MAAM,YAAY,GAA8C,EAAE,CAAC;YACnE,KAAK,MAAM,IAAI,IAAI,CAAC,YAAY,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAU,EAAE,CAAC;gBAC5E,IAAI,KAAK,CAAC,YAAY,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC;oBAC/B,YAAY,CAAC,IAAI,CAAC,GAAG,IAAI,CAAC,IAAI,CAAC,SAAS,EAAE,GAAG,KAAK,CAAC,SAAS,KAAK,IAAI,EAAE,CAAC,CAAC;gBAC3E,CAAC;YACH,CAAC;YACD,OAAO;gBACL,IAAI,EAAE,KAAK,CAAC,SAAS;gBACrB,WAAW,EAAE,KAAK,CAAC,WAAW;gBAC9B,IAAI,EAAE,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,SAAS;gBAClE,OAAO,EAAE,KAAK,CAAC,OAAO;gBACtB,eAAe;gBACf,YAAY,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,SAAS;aAC9E,CAAC;QACJ,CAAC,CAAC;KACH,CAAC;IAEF,yEAAyE;IACzE,sEAAsE;IACtE,MAAM,WAAW,GAAG,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,aAAa,CAAC,CAAC,CAAC,CAAC,CAAC;IAC7D,MAAM,IAAA,iBAAS,EAAC,IAAI,CAAC,IAAI,CAAC,MAAM,EAAE,eAAe,CAAC,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,WAAW,EAAE,CAAC,CAAC;AACxF,CAAC;AAED,SAAS,aAAa,CAAoC,GAAM;IAC9D,MAAM,GAAG,GAAe,EAAE,CAAC;IAC3B,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,EAAE,CAAC;QACzC,IAAI,CAAC,KAAK,SAAS;YAAE,GAAG,CAAC,CAAY,CAAC,GAAG,CAAe,CAAC;IAC3D,CAAC;IACD,OAAO,GAAG,CAAC;AACb,CAAC","sourcesContent":["/**\n * Issue #411 — write `bin-meta.json` next to the CLI bundle. The per-bin\n * `<bin> install -p claude|codex` reads this sidecar at runtime to drive\n * the shared plugin-emitter without re-running schema extraction.\n *\n * Shape:\n * {\n * \"name\": \"<bin>\",\n * \"version\": \"<bin-version>\",\n * \"description\": \"<from cliConfig or package.json>\",\n * \"mcpDefault\": { \"command\": \"<bin>\", \"args\": [\"serve\", \"--stdio\"] },\n * \"prompts\": [{ \"name\", \"description\", \"arguments\": [...] }],\n * \"skills\": [{ \"name\", \"description\", \"instructionFile\" (path under _skills/), \"resourceDirs\": { references?, examples?, scripts?, assets? } }]\n * }\n */\n\nimport * as path from 'path';\n\nimport { writeJSON } from '@frontmcp/utils';\n\nimport type { FrontmcpExecConfig } from './config';\nimport type { ExtractedSchema } from './cli-runtime/schema-extractor';\n\nexport interface BinMeta {\n name: string;\n version: string;\n description: string;\n mcpDefault: { command: string; args: string[] };\n prompts: Array<{\n name: string;\n description?: string;\n arguments?: Array<{ name: string; description?: string; required?: boolean }>;\n }>;\n skills: Array<{\n name: string;\n description?: string;\n tags?: string[];\n license?: string;\n instructionFile?: string;\n resourceDirs?: { references?: string; examples?: string; scripts?: string; assets?: string };\n }>;\n}\n\nexport async function writeBinMeta(\n outDir: string,\n config: FrontmcpExecConfig,\n schema: ExtractedSchema,\n): Promise<void> {\n const meta: BinMeta = {\n name: config.name,\n version: config.version ?? '0.0.0',\n description: config.cli?.description ?? `${config.name} (FrontMCP server)`,\n mcpDefault: { command: config.name, args: ['serve', '--stdio'] },\n prompts: schema.prompts.map((p) => ({\n name: p.name,\n description: p.description,\n arguments: p.arguments,\n })),\n skills: schema.skillAssets.map((asset) => {\n // The bin runtime expects RELATIVE paths under `_skills/` so the bundle\n // is portable across install destinations.\n const instructionFile = asset.instructionFile\n ? path.join('_skills', `${asset.skillName}--${path.basename(asset.instructionFile)}`)\n : undefined;\n const resourceDirs: BinMeta['skills'][number]['resourceDirs'] = {};\n for (const kind of ['references', 'examples', 'scripts', 'assets'] as const) {\n if (asset.resourceDirs?.[kind]) {\n resourceDirs[kind] = path.join('_skills', `${asset.skillName}--${kind}`);\n }\n }\n return {\n name: asset.skillName,\n description: asset.description,\n tags: asset.tags && asset.tags.length > 0 ? asset.tags : undefined,\n license: asset.license,\n instructionFile,\n resourceDirs: Object.keys(resourceDirs).length > 0 ? resourceDirs : undefined,\n };\n }),\n };\n\n // Drop undefined fields so the JSON payload matches the TS interface (no\n // explicit \"description\": undefined keys land in stringified output).\n const cleanSkills = meta.skills.map((s) => omitUndefined(s));\n await writeJSON(path.join(outDir, 'bin-meta.json'), { ...meta, skills: cleanSkills });\n}\n\nfunction omitUndefined<T extends Record<string, unknown>>(obj: T): Partial<T> {\n const out: Partial<T> = {};\n for (const [k, v] of Object.entries(obj)) {\n if (v !== undefined) out[k as keyof T] = v as T[keyof T];\n }\n return out;\n}\n"]}
@@ -1362,16 +1362,158 @@ function generateInstallCommand(appName, nativeDeps, selfContained) {
1362
1362
  depEntries.push(` { name: ${JSON.stringify(pkg)}, type: 'npm', install: 'npm install ${pkg}', check: 'npm ls ${pkg}' }`);
1363
1363
  }
1364
1364
  }
1365
- return `program
1365
+ return `function _frontmcpCollectArg(value, acc) { return Array.isArray(acc) ? acc.concat(value) : [value]; }
1366
+
1367
+ program
1366
1368
  .command('install')
1367
- .description('Install to ~/.frontmcp/ and set up dependencies')
1369
+ .description('Install to ~/.frontmcp/ and set up dependencies, OR emit an IDE plugin (use -p)')
1368
1370
  .option('--prefix <path>', 'Installation prefix directory')
1369
1371
  .option('--bin-dir <path>', 'Directory for symlink (default: ~/.local/bin or /usr/local/bin)')
1372
+ .option('-p, --provider <provider>', 'Emit plugin for provider: claude | codex (repeatable)', _frontmcpCollectArg, [])
1373
+ .option('--scope <scope>', 'Plugin scope when -p is set: project | user', 'project')
1374
+ .option('--no-skills', 'Skip the skills/ subtree (when -p claude)')
1375
+ .option('--no-commands', 'Skip the commands/ subtree (when -p claude)')
1376
+ .option('--only-mcp', 'Skip plugin folder; just register the MCP server')
1377
+ .option('--command <cmd>', 'Override MCP server invocation in the plugin manifest')
1378
+ .option('--env <name>', 'Add env-var placeholder to plugin manifest (repeatable)', _frontmcpCollectArg, [])
1379
+ .option('--dir <dir>', 'Override plugin destination root')
1380
+ .option('--dry-run', 'Print plan; do not write')
1381
+ .option('--status', 'Print install status per provider; exit 0')
1370
1382
  .action(async function(opts) {
1371
1383
  var fs = require('fs');
1372
1384
  var pathMod = require('path');
1373
1385
  var os = require('os');
1374
1386
  var exec = require('child_process').execSync;
1387
+
1388
+ // Issue #411 — when -p is set OR --status is set, run the plugin-install
1389
+ // path instead of the legacy bundle-copy + symlink behavior.
1390
+ var providers = Array.isArray(opts.provider) ? opts.provider : [];
1391
+ if (opts.status || providers.length > 0) {
1392
+ var emitter = require('./plugin-emitter');
1393
+ var binMetaPath = pathMod.join(SCRIPT_DIR, 'bin-meta.json');
1394
+ var meta;
1395
+ try { meta = JSON.parse(fs.readFileSync(binMetaPath, 'utf8')); }
1396
+ catch (e) {
1397
+ console.error('Could not read bin-meta.json at ' + binMetaPath + '. Was the bin built with a recent frontmcp?');
1398
+ process.exit(1);
1399
+ }
1400
+ var pkgJsonPath = pathMod.join(__dirname, '..', '..', 'package.json');
1401
+ var cliVersion = '0.0.0';
1402
+ try { cliVersion = (require(pkgJsonPath) || {}).version || '0.0.0'; } catch (e) { /* ok */ }
1403
+
1404
+ function resolveDestRoot() {
1405
+ if (opts.dir) return pathMod.resolve(opts.dir);
1406
+ if (opts.scope === 'user') return pathMod.join(os.homedir(), '.claude', 'plugins');
1407
+ return pathMod.join(process.cwd(), '.claude', 'plugins');
1408
+ }
1409
+
1410
+ if (opts.status) {
1411
+ console.log(meta.name + ' install --status');
1412
+ var destRootSt = resolveDestRoot();
1413
+ var pluginDirSt = pathMod.join(destRootSt, meta.name);
1414
+ var installed = await emitter.readInstalledPluginVersion(pluginDirSt);
1415
+ if (installed) {
1416
+ var tag = installed === meta.version ? 'installed' : 'outdated';
1417
+ console.log(' claude: ' + tag + ' v' + installed + (tag === 'outdated' ? ' (bin at v' + meta.version + ')' : '') + ' at ' + pluginDirSt);
1418
+ } else {
1419
+ console.log(' claude: not installed at ' + pluginDirSt);
1420
+ }
1421
+ var codexConfigSt = pathMod.join(os.homedir(), '.codex', 'config.toml');
1422
+ if (fs.existsSync(codexConfigSt) && fs.readFileSync(codexConfigSt, 'utf8').indexOf('# frontmcp:codex-start:' + meta.name) !== -1) {
1423
+ console.log(' codex: installed entry for ' + meta.name + ' in ' + codexConfigSt);
1424
+ } else {
1425
+ console.log(' codex: not installed in ' + codexConfigSt);
1426
+ }
1427
+ return;
1428
+ }
1429
+
1430
+ function buildSkills() {
1431
+ if (opts.skills === false || opts.onlyMcp) return [];
1432
+ var out = [];
1433
+ for (var i = 0; i < (meta.skills || []).length; i++) {
1434
+ var s = meta.skills[i];
1435
+ var resourceDirs = {};
1436
+ if (s.resourceDirs) {
1437
+ for (var k in s.resourceDirs) {
1438
+ if (Object.prototype.hasOwnProperty.call(s.resourceDirs, k)) {
1439
+ resourceDirs[k] = pathMod.join(SCRIPT_DIR, s.resourceDirs[k]);
1440
+ }
1441
+ }
1442
+ }
1443
+ out.push({
1444
+ name: s.name,
1445
+ description: s.description || (s.name + ' skill from ' + meta.name),
1446
+ tags: Array.isArray(s.tags) && s.tags.length > 0 ? s.tags : undefined,
1447
+ license: s.license || undefined,
1448
+ instructionFile: s.instructionFile ? pathMod.join(SCRIPT_DIR, s.instructionFile) : undefined,
1449
+ resourceDirs: Object.keys(resourceDirs).length > 0 ? resourceDirs : undefined,
1450
+ });
1451
+ }
1452
+ return out;
1453
+ }
1454
+
1455
+ function buildCommands() {
1456
+ if (opts.commands === false || opts.onlyMcp) return [];
1457
+ return (meta.prompts || []).map(function(p) {
1458
+ return { name: p.name, description: p.description, arguments: p.arguments };
1459
+ });
1460
+ }
1461
+
1462
+ for (var pi = 0; pi < providers.length; pi++) {
1463
+ var provider = providers[pi];
1464
+ if (provider === 'claude') {
1465
+ var destRoot = resolveDestRoot();
1466
+ var result = await emitter.emitClaudePlugin({
1467
+ destRoot: destRoot,
1468
+ name: meta.name,
1469
+ version: meta.version,
1470
+ description: meta.description,
1471
+ mcpCommand: opts.command || meta.mcpDefault.command,
1472
+ mcpArgs: meta.mcpDefault.args,
1473
+ envHints: Array.isArray(opts.env) ? opts.env : [],
1474
+ skills: buildSkills(),
1475
+ commands: buildCommands(),
1476
+ cliVersion: cliVersion,
1477
+ dryRun: opts.dryRun,
1478
+ });
1479
+ if (opts.dryRun) {
1480
+ console.log('[install:claude] dry-run plan');
1481
+ console.log(' pluginDir: ' + result.pluginDir);
1482
+ console.log(' filesWritten (planned):');
1483
+ for (var fwi = 0; fwi < result.filesWritten.length; fwi++) console.log(' + ' + result.filesWritten[fwi]);
1484
+ } else {
1485
+ console.log('✓ Wrote ' + result.pluginDir + '/ (' + (result.manifest.skills || []).length + ' skills, ' + ((result.manifest.commands || []).length) + ' commands, 1 MCP server)');
1486
+ console.log(' Restart Claude Code (or run /plugins reload) to pick up the plugin.');
1487
+ }
1488
+ } else if (provider === 'codex') {
1489
+ var codexConfig = pathMod.join(os.homedir(), '.codex', 'config.toml');
1490
+ var env = {};
1491
+ var envList = Array.isArray(opts.env) ? opts.env : [];
1492
+ for (var ei = 0; ei < envList.length; ei++) env[envList[ei]] = '${'$'}{' + envList[ei] + '}';
1493
+ var codexResult = await emitter.emitCodexEntry({
1494
+ configPath: codexConfig,
1495
+ name: meta.name,
1496
+ command: opts.command || meta.mcpDefault.command,
1497
+ args: meta.mcpDefault.args,
1498
+ env: env,
1499
+ dryRun: opts.dryRun,
1500
+ });
1501
+ if (opts.dryRun) {
1502
+ console.log('[install:codex] dry-run plan');
1503
+ console.log(' configPath: ' + codexConfig);
1504
+ console.log(codexResult.configContent);
1505
+ } else {
1506
+ console.log('✓ Updated ' + codexConfig + ' with [[mcp_servers]] entry for ' + meta.name);
1507
+ }
1508
+ } else {
1509
+ console.error('Unknown provider: ' + provider);
1510
+ process.exitCode = 1;
1511
+ return;
1512
+ }
1513
+ }
1514
+ return;
1515
+ }
1516
+
1375
1517
  var installBase = opts.prefix || FRONTMCP_HOME;
1376
1518
  var appDir = pathMod.join(installBase, 'apps', ${JSON.stringify(appName)});
1377
1519
  var dirs = ['', '/data', '/sessions', '/credentials'].map(function(s) { return appDir + s; });
@@ -1432,13 +1574,63 @@ ${depEntries.join(',\n')}
1432
1574
 
1433
1575
  program
1434
1576
  .command('uninstall')
1435
- .description('Remove from ~/.frontmcp/ and clean up')
1577
+ .description('Remove from ~/.frontmcp/, OR remove an IDE plugin (use -p)')
1436
1578
  .option('--prefix <path>', 'Installation prefix directory')
1437
1579
  .option('--bin-dir <path>', 'Directory where symlink was created')
1580
+ .option('-p, --provider <provider>', 'Remove plugin for provider: claude | codex (repeatable)', _frontmcpCollectArg, [])
1581
+ .option('--scope <scope>', 'Plugin scope when -p is set: project | user', 'project')
1582
+ .option('--dir <dir>', 'Override plugin destination root')
1438
1583
  .action(async function(opts) {
1439
1584
  var fs = require('fs');
1440
1585
  var pathMod = require('path');
1441
1586
  var os = require('os');
1587
+
1588
+ // Issue #411 — when -p is set, route through the shared plugin-emitter
1589
+ // to remove the IDE plugin instead of the legacy ~/.frontmcp uninstall.
1590
+ var providers = Array.isArray(opts.provider) ? opts.provider : [];
1591
+ if (providers.length > 0) {
1592
+ var emitter = require('./plugin-emitter');
1593
+ var binMetaPath = pathMod.join(SCRIPT_DIR, 'bin-meta.json');
1594
+ var meta;
1595
+ try { meta = JSON.parse(fs.readFileSync(binMetaPath, 'utf8')); }
1596
+ catch (e) {
1597
+ console.error('Could not read bin-meta.json at ' + binMetaPath + '. Was the bin built with a recent frontmcp?');
1598
+ process.exit(1);
1599
+ }
1600
+
1601
+ function resolveDestRoot() {
1602
+ if (opts.dir) return pathMod.resolve(opts.dir);
1603
+ if (opts.scope === 'user') return pathMod.join(os.homedir(), '.claude', 'plugins');
1604
+ return pathMod.join(process.cwd(), '.claude', 'plugins');
1605
+ }
1606
+
1607
+ for (var pi = 0; pi < providers.length; pi++) {
1608
+ var provider = providers[pi];
1609
+ if (provider === 'claude') {
1610
+ var destRoot = resolveDestRoot();
1611
+ var result = await emitter.removeClaudePlugin({ destRoot: destRoot, name: meta.name });
1612
+ if (result.removed.length === 0) {
1613
+ console.log(' claude: nothing to remove at ' + result.pluginDir);
1614
+ } else {
1615
+ console.log('✓ Removed ' + result.removed.length + ' file(s) from ' + result.pluginDir);
1616
+ }
1617
+ } else if (provider === 'codex') {
1618
+ var codexConfig = pathMod.join(os.homedir(), '.codex', 'config.toml');
1619
+ var codexResult = await emitter.removeCodexEntry({ configPath: codexConfig, name: meta.name });
1620
+ if (codexResult.removed) {
1621
+ console.log('✓ Removed [[mcp_servers]] entry for ' + meta.name + ' from ' + codexConfig);
1622
+ } else {
1623
+ console.log(' codex: no entry for ' + meta.name + ' in ' + codexConfig);
1624
+ }
1625
+ } else {
1626
+ console.error('Unknown provider: ' + provider);
1627
+ process.exitCode = 1;
1628
+ return;
1629
+ }
1630
+ }
1631
+ return;
1632
+ }
1633
+
1442
1634
  var uninstallBase = opts.prefix || FRONTMCP_HOME;
1443
1635
  var appDir = pathMod.join(uninstallBase, 'apps', ${JSON.stringify(appName)});
1444
1636