@orchid-labs/pluxx 0.1.13 → 0.1.14

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/dist/cli/index.js CHANGED
@@ -85,7 +85,7 @@ var require_src = __commonJS({
85
85
  });
86
86
 
87
87
  // src/cli/index.ts
88
- import { readFileSync as readFileSync19 } from "fs";
88
+ import { readFileSync as readFileSync20 } from "fs";
89
89
 
90
90
  // src/config/load.ts
91
91
  import { resolve, extname, dirname } from "path";
@@ -4462,10 +4462,20 @@ var McpServerSchema = external_exports.preprocess(
4462
4462
  external_exports.discriminatedUnion("transport", [McpServerHttpSchema, McpServerSseSchema, McpServerStdioSchema])
4463
4463
  );
4464
4464
  var HookEntrySchema = external_exports.object({
4465
- type: external_exports.enum(["command", "prompt"]).default("command"),
4465
+ type: external_exports.enum(["command", "http", "mcp_tool", "prompt", "agent"]).default("command"),
4466
4466
  command: external_exports.string().optional(),
4467
4467
  prompt: external_exports.string().optional(),
4468
4468
  model: external_exports.string().optional(),
4469
+ url: external_exports.string().url().optional(),
4470
+ headers: external_exports.record(external_exports.string(), external_exports.string()).optional(),
4471
+ allowedEnvVars: external_exports.array(external_exports.string()).optional(),
4472
+ server: external_exports.string().optional(),
4473
+ tool: external_exports.string().optional(),
4474
+ input: external_exports.record(external_exports.string(), external_exports.unknown()).optional(),
4475
+ if: external_exports.string().optional(),
4476
+ async: external_exports.boolean().optional(),
4477
+ asyncRewake: external_exports.boolean().optional(),
4478
+ shell: external_exports.enum(["bash"]).optional(),
4469
4479
  timeout: external_exports.number().optional(),
4470
4480
  matcher: external_exports.union([external_exports.string(), external_exports.record(external_exports.string(), external_exports.unknown())]).optional(),
4471
4481
  failClosed: external_exports.boolean().optional(),
@@ -4478,11 +4488,34 @@ var HookEntrySchema = external_exports.object({
4478
4488
  message: "Command hooks require a command."
4479
4489
  });
4480
4490
  }
4481
- if (entry.type === "prompt" && !entry.prompt) {
4491
+ if (entry.type === "http" && !entry.url) {
4492
+ ctx.addIssue({
4493
+ code: external_exports.ZodIssueCode.custom,
4494
+ path: ["url"],
4495
+ message: "HTTP hooks require a url."
4496
+ });
4497
+ }
4498
+ if (entry.type === "mcp_tool") {
4499
+ if (!entry.server) {
4500
+ ctx.addIssue({
4501
+ code: external_exports.ZodIssueCode.custom,
4502
+ path: ["server"],
4503
+ message: "MCP tool hooks require a server."
4504
+ });
4505
+ }
4506
+ if (!entry.tool) {
4507
+ ctx.addIssue({
4508
+ code: external_exports.ZodIssueCode.custom,
4509
+ path: ["tool"],
4510
+ message: "MCP tool hooks require a tool."
4511
+ });
4512
+ }
4513
+ }
4514
+ if ((entry.type === "prompt" || entry.type === "agent") && !entry.prompt) {
4482
4515
  ctx.addIssue({
4483
4516
  code: external_exports.ZodIssueCode.custom,
4484
4517
  path: ["prompt"],
4485
- message: "Prompt hooks require a prompt."
4518
+ message: `${entry.type === "agent" ? "Agent" : "Prompt"} hooks require a prompt.`
4486
4519
  });
4487
4520
  }
4488
4521
  });
@@ -4896,7 +4929,7 @@ async function loadConfig(dir = process.cwd()) {
4896
4929
 
4897
4930
  // src/generators/index.ts
4898
4931
  import { rmSync, mkdirSync as mkdirSync3 } from "fs";
4899
- import { resolve as resolve10, relative as relative6 } from "path";
4932
+ import { resolve as resolve11, relative as relative6 } from "path";
4900
4933
 
4901
4934
  // src/generators/base.ts
4902
4935
  import { resolve as resolve4, join, relative } from "path";
@@ -4910,6 +4943,7 @@ var CompilerIntentSkillPolicySchema = external_exports.object({
4910
4943
  skillDir: external_exports.string(),
4911
4944
  title: external_exports.string(),
4912
4945
  description: external_exports.string().optional(),
4946
+ sourceFrontmatter: external_exports.record(external_exports.unknown()).optional(),
4913
4947
  source: external_exports.object({
4914
4948
  kind: external_exports.literal("claude-allowed-tools"),
4915
4949
  platform: external_exports.literal("claude-code").default("claude-code")
@@ -5175,14 +5209,28 @@ var Generator = class {
5175
5209
  };
5176
5210
 
5177
5211
  // src/generators/shared/claude-family.ts
5178
- import { existsSync as existsSync6 } from "fs";
5179
- import { resolve as resolve6 } from "path";
5212
+ import { resolve as resolve7 } from "path";
5180
5213
 
5181
5214
  // src/hook-translation-registry.ts
5182
5215
  var HOOK_PLATFORM_REGISTRY = {
5183
5216
  "claude-code": {
5184
5217
  fields: {
5185
- prompt: { mode: "drop" },
5218
+ prompt: {
5219
+ mode: "preserve",
5220
+ supportedEvents: [
5221
+ "PermissionRequest",
5222
+ "PostToolBatch",
5223
+ "PostToolUse",
5224
+ "PostToolUseFailure",
5225
+ "PreToolUse",
5226
+ "Stop",
5227
+ "SubagentStop",
5228
+ "TaskCompleted",
5229
+ "TaskCreated",
5230
+ "UserPromptExpansion",
5231
+ "UserPromptSubmit"
5232
+ ]
5233
+ },
5186
5234
  matcher: { mode: "preserve" },
5187
5235
  failClosed: { mode: "drop" },
5188
5236
  loop_limit: { mode: "drop" }
@@ -5197,8 +5245,8 @@ var HOOK_PLATFORM_REGISTRY = {
5197
5245
  }
5198
5246
  },
5199
5247
  codex: {
5200
- supportedEvents: ["SessionStart", "PreToolUse", "PostToolUse", "UserPromptSubmit", "Stop"],
5201
- unsupportedEventReason: "Codex currently documents only SessionStart, PreToolUse, PostToolUse, UserPromptSubmit, and Stop for hook configuration.",
5248
+ supportedEvents: ["SessionStart", "PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "Stop"],
5249
+ unsupportedEventReason: "Codex currently documents only SessionStart, PreToolUse, PermissionRequest, PostToolUse, UserPromptSubmit, and Stop for hook configuration.",
5202
5250
  fields: {
5203
5251
  prompt: { mode: "drop" },
5204
5252
  matcher: { mode: "preserve" },
@@ -5304,7 +5352,7 @@ function warnDroppedHookFields(platform, event, entries) {
5304
5352
  const hasMatcher = entries.some((entry) => entry.matcher !== void 0);
5305
5353
  const hasFailClosed = entries.some((entry) => entry.failClosed !== void 0);
5306
5354
  const hasLoopLimit = entries.some((entry) => entry.loop_limit !== void 0);
5307
- if (hasPromptHooks) {
5355
+ if (hasPromptHooks && !isHookFieldPreserved(platform, "prompt", event)) {
5308
5356
  console.warn(
5309
5357
  `[pluxx] ${platform} generator dropped unsupported prompt-based hook for event "${event}".`
5310
5358
  );
@@ -5675,7 +5723,7 @@ function getEnabledRuntimeReadinessBindings(capability, plan) {
5675
5723
  });
5676
5724
  }
5677
5725
  var NAMED_PROMPT_TARGET_NOTE = "Named `skills` / `commands` readiness targets currently translate through prompt-entry gating with best-effort matching because the core four do not share one exact per-skill or per-command runtime interception surface.";
5678
- var CODEX_EXTERNAL_NOTE = "Codex readiness currently translates into generated hook/config guidance rather than an enforced plugin-bundled runtime surface.";
5726
+ var CODEX_EXTERNAL_NOTE = "Codex readiness now bundles translated hooks in the plugin, but Pluxx still emits `.codex/readiness.generated.json` and `.codex/hooks.generated.json` companion guidance because some Codex runtimes still gate hook activation behind `codex_hooks`.";
5679
5727
  function getRuntimeReadinessNamedPromptTargetNote() {
5680
5728
  return NAMED_PROMPT_TARGET_NOTE;
5681
5729
  }
@@ -5744,7 +5792,7 @@ function getRuntimeReadinessCapability(platform, pluginRootVar = "PLUGIN_ROOT")
5744
5792
  bundleEnforced: false,
5745
5793
  namedPromptTargetScope: "best-effort",
5746
5794
  scriptPath: ".codex/pluxx-readiness.mjs",
5747
- companionArtifacts: [".codex/readiness.generated.json", ".codex/hooks.generated.json"],
5795
+ companionArtifacts: [".codex/readiness.generated.json", "hooks/hooks.json", ".codex/hooks.generated.json"],
5748
5796
  bindings: [
5749
5797
  {
5750
5798
  gate: "session-start",
@@ -5794,9 +5842,37 @@ function getRuntimeReadinessCapability(platform, pluginRootVar = "PLUGIN_ROOT")
5794
5842
  }
5795
5843
  }
5796
5844
 
5845
+ // src/instructions.ts
5846
+ import { existsSync as existsSync5, readFileSync as readFileSync2 } from "fs";
5847
+ import { resolve as resolve5 } from "path";
5848
+ function resolveInstructionsPath(rootDir, config) {
5849
+ if (!config.instructions) return null;
5850
+ const instructionsPath = resolve5(rootDir, config.instructions);
5851
+ return existsSync5(instructionsPath) ? instructionsPath : null;
5852
+ }
5853
+ async function readInstructionsContent(rootDir, config) {
5854
+ const instructionsPath = resolveInstructionsPath(rootDir, config);
5855
+ if (!instructionsPath) return null;
5856
+ return readTextFile(instructionsPath);
5857
+ }
5858
+ function readInstructionsContentSync(rootDir, config) {
5859
+ const instructionsPath = resolveInstructionsPath(rootDir, config);
5860
+ if (!instructionsPath) return null;
5861
+ return readFileSync2(instructionsPath, "utf-8");
5862
+ }
5863
+ function renderTitledInstructionsDocument(config, content, titleSuffix = "Plugin") {
5864
+ return [
5865
+ `# ${config.brand?.displayName ?? config.name} ${titleSuffix}`,
5866
+ "",
5867
+ config.brand?.shortDescription ?? config.description,
5868
+ "",
5869
+ content
5870
+ ].join("\n");
5871
+ }
5872
+
5797
5873
  // src/agents.ts
5798
- import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync2, statSync } from "fs";
5799
- import { basename, resolve as resolve5 } from "path";
5874
+ import { existsSync as existsSync6, readdirSync, readFileSync as readFileSync3, statSync } from "fs";
5875
+ import { basename, resolve as resolve6 } from "path";
5800
5876
  function firstHeading(content) {
5801
5877
  const lines = content.split(/\r?\n/);
5802
5878
  for (const line of lines) {
@@ -5873,7 +5949,7 @@ function walkMarkdownFiles(dir) {
5873
5949
  const entries = readdirSync(dir);
5874
5950
  const files = [];
5875
5951
  for (const entry of entries) {
5876
- const fullPath = resolve5(dir, entry);
5952
+ const fullPath = resolve6(dir, entry);
5877
5953
  const stat = statSync(fullPath);
5878
5954
  if (stat.isDirectory()) {
5879
5955
  files.push(...walkMarkdownFiles(fullPath));
@@ -5886,7 +5962,7 @@ function walkMarkdownFiles(dir) {
5886
5962
  return files;
5887
5963
  }
5888
5964
  function parseCanonicalAgentFile(agentPath) {
5889
- const content = readFileSync2(agentPath, "utf-8");
5965
+ const content = readFileSync3(agentPath, "utf-8");
5890
5966
  const { frontmatterLines, body } = splitMarkdownFrontmatter(content);
5891
5967
  const frontmatter = parseAgentFrontmatter(frontmatterLines);
5892
5968
  const fileStem = basename(agentPath, ".md");
@@ -5902,31 +5978,52 @@ function parseCanonicalAgentFile(agentPath) {
5902
5978
  };
5903
5979
  }
5904
5980
  function readCanonicalAgentFiles(agentsDir) {
5905
- if (!agentsDir || !existsSync5(agentsDir)) return [];
5981
+ if (!agentsDir || !existsSync6(agentsDir)) return [];
5906
5982
  return walkMarkdownFiles(agentsDir).sort((a, b) => a.localeCompare(b)).map(parseCanonicalAgentFile);
5907
5983
  }
5908
-
5909
- // src/generators/shared/claude-family.ts
5910
- async function generateClaudeFamilyOutputs(args2) {
5911
- const {
5912
- config,
5913
- rootDir,
5914
- platform,
5915
- options,
5916
- writeJson,
5917
- writeFile: writeFile3
5918
- } = args2;
5919
- await Promise.all([
5920
- writeManifest(config, rootDir, options, writeJson),
5921
- writeMcpConfig(config, platform, writeJson),
5922
- writeHooks(config, platform, options, writeJson, writeFile3),
5923
- writeInstructions(config, rootDir, options, writeFile3)
5924
- ]);
5984
+ function getCanonicalAgentMetadata(agent) {
5985
+ const frontmatter = agent.frontmatter;
5986
+ return {
5987
+ name: agent.name,
5988
+ description: agent.description ?? `${agent.name} specialist.`,
5989
+ body: agent.body,
5990
+ mode: asString(frontmatter.mode),
5991
+ hidden: frontmatter.hidden === true,
5992
+ model: asString(frontmatter.model),
5993
+ modelReasoningEffort: asString(frontmatter.model_reasoning_effort) ?? asString(frontmatter.effort),
5994
+ sandboxMode: asString(frontmatter.sandbox_mode),
5995
+ temperature: asNumber(frontmatter.temperature),
5996
+ steps: asNumber(frontmatter.steps) ?? asNumber(frontmatter.maxSteps),
5997
+ disabled: asBoolean(frontmatter.disable),
5998
+ color: asString(frontmatter.color),
5999
+ topP: asNumber(frontmatter.topP) ?? asNumber(frontmatter.top_p),
6000
+ skills: asString(frontmatter.skills),
6001
+ memory: asString(frontmatter.memory),
6002
+ background: asBoolean(frontmatter.background),
6003
+ isolation: asString(frontmatter.isolation),
6004
+ permission: asMap(frontmatter.permission),
6005
+ tools: frontmatter.tools
6006
+ };
5925
6007
  }
6008
+ function asString(value) {
6009
+ return typeof value === "string" && value ? value : void 0;
6010
+ }
6011
+ function asNumber(value) {
6012
+ return typeof value === "number" ? value : void 0;
6013
+ }
6014
+ function asBoolean(value) {
6015
+ return typeof value === "boolean" ? value : void 0;
6016
+ }
6017
+ function asMap(value) {
6018
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
6019
+ return value;
6020
+ }
6021
+
6022
+ // src/hook-command-env.ts
5926
6023
  function shellSingleQuote(value) {
5927
6024
  return `'${value.replace(/'/g, `'"'"'`)}'`;
5928
6025
  }
5929
- function buildClaudeHookCommandWrapperScript(command2) {
6026
+ function buildHookCommandWrapperScript(command2, pluginRootVar, envFileVar) {
5930
6027
  const serializedCommand = shellSingleQuote(command2);
5931
6028
  const exportLoader = [
5932
6029
  'import { readFileSync } from "node:fs"',
@@ -5945,20 +6042,23 @@ function buildClaudeHookCommandWrapperScript(command2) {
5945
6042
  " process.stdout.write(`export ${key}=${shellSingleQuote(value)}\\0`)",
5946
6043
  "}"
5947
6044
  ].join("\n");
6045
+ const maybeAppendEnvFile = envFileVar ? [
6046
+ ` if [ -n "\${${envFileVar}:-}" ]; then`,
6047
+ ` printf '%s\\n' "$pluxx_export" >> "\${${envFileVar}}"`,
6048
+ " fi"
6049
+ ] : [];
5948
6050
  return [
5949
6051
  "#!/usr/bin/env bash",
5950
6052
  "set -euo pipefail",
5951
6053
  "",
5952
- 'PLUXX_PLUGIN_ROOT="${CLAUDE_PLUGIN_ROOT:-$(cd "$(dirname "$0")/.." && pwd)}"',
6054
+ `PLUXX_PLUGIN_ROOT="\${${pluginRootVar}:-$(cd "$(dirname "$0")/.." && pwd)}"`,
5953
6055
  'PLUXX_USER_CONFIG_PATH="$PLUXX_PLUGIN_ROOT/.pluxx-user.json"',
5954
6056
  "",
5955
6057
  'if [ -f "$PLUXX_USER_CONFIG_PATH" ]; then',
5956
6058
  " while IFS= read -r -d '' pluxx_export; do",
5957
6059
  ' if [ -n "$pluxx_export" ]; then',
5958
6060
  ' eval "$pluxx_export"',
5959
- ' if [ -n "${CLAUDE_ENV_FILE:-}" ]; then',
5960
- ` printf '%s\\n' "$pluxx_export" >> "$CLAUDE_ENV_FILE"`,
5961
- " fi",
6061
+ ...maybeAppendEnvFile,
5962
6062
  " fi",
5963
6063
  " done < <(",
5964
6064
  ` node --input-type=module -e ${shellSingleQuote(exportLoader)} "$PLUXX_USER_CONFIG_PATH"`,
@@ -5970,6 +6070,24 @@ function buildClaudeHookCommandWrapperScript(command2) {
5970
6070
  ""
5971
6071
  ].join("\n");
5972
6072
  }
6073
+
6074
+ // src/generators/shared/claude-family.ts
6075
+ async function generateClaudeFamilyOutputs(args2) {
6076
+ const {
6077
+ config,
6078
+ rootDir,
6079
+ platform,
6080
+ options,
6081
+ writeJson,
6082
+ writeFile: writeFile3
6083
+ } = args2;
6084
+ await Promise.all([
6085
+ writeManifest(config, rootDir, options, writeJson),
6086
+ writeMcpConfig(config, platform, writeJson),
6087
+ writeHooks(config, platform, options, writeJson, writeFile3),
6088
+ writeInstructions(config, rootDir, options, writeFile3)
6089
+ ]);
6090
+ }
5973
6091
  async function writeManifest(config, rootDir, options, writeJson) {
5974
6092
  const manifest = {
5975
6093
  name: config.name,
@@ -5991,7 +6109,7 @@ async function writeManifest(config, rootDir, options, writeJson) {
5991
6109
  if (config.agents && agentsManifestMode === "directory") {
5992
6110
  manifest.agents = "./agents/";
5993
6111
  } else if (config.agents && agentsManifestMode === "files") {
5994
- const agentsDir = resolve6(rootDir, config.agents);
6112
+ const agentsDir = resolve7(rootDir, config.agents);
5995
6113
  const agents = readCanonicalAgentFiles(agentsDir);
5996
6114
  if (agents.length > 0) {
5997
6115
  manifest.agents = agents.map((agent) => `./agents/${agent.fileStem}.md`);
@@ -6107,51 +6225,112 @@ async function writeHooks(config, platform, options, writeJson, writeFile3) {
6107
6225
  }
6108
6226
  for (const [event, entries] of Object.entries(config.hooks)) {
6109
6227
  if (!entries) continue;
6110
- warnDroppedHookFields(platform, event, entries);
6111
6228
  const mappedEvent = mapEventName(event);
6112
- const commandEntries = entries.filter((entry) => {
6113
- if (entry.type === "prompt" || !entry.command) return false;
6114
- if (usesPlatformManagedAuth && entry.command.includes("check-env.sh")) {
6115
- return false;
6116
- }
6117
- return true;
6118
- });
6119
- if (commandEntries.length === 0) continue;
6229
+ warnDroppedHookFields(platform, mappedEvent, entries);
6230
+ const translatedEntries = (await Promise.all(entries.map(async (entry) => mapClaudeHookEntry({
6231
+ entry,
6232
+ mappedEvent,
6233
+ options,
6234
+ shouldWrapClaudeHookCommands,
6235
+ usesPlatformManagedAuth,
6236
+ writeFile: writeFile3,
6237
+ nextWrapperIndex: () => {
6238
+ generatedClaudeHookCommandCount += 1;
6239
+ return generatedClaudeHookCommandCount;
6240
+ }
6241
+ })))).filter((entry) => entry !== null);
6242
+ if (translatedEntries.length === 0) continue;
6120
6243
  hooks[mappedEvent] = [
6121
6244
  ...hooks[mappedEvent] ?? [],
6122
- ...await Promise.all(commandEntries.map(async (entry) => {
6123
- const command2 = entry.command.replace("${PLUGIN_ROOT}", `\${${options.pluginRootVar}}`);
6124
- const finalCommand = shouldWrapClaudeHookCommands ? await (async () => {
6125
- generatedClaudeHookCommandCount += 1;
6126
- const relativePath = `hooks/pluxx-hook-command-${generatedClaudeHookCommandCount}.sh`;
6127
- await writeFile3(relativePath, buildClaudeHookCommandWrapperScript(command2));
6128
- return `bash "\${${options.pluginRootVar}}/${relativePath}"`;
6129
- })() : command2;
6130
- return {
6131
- ...entry.matcher !== void 0 ? { matcher: entry.matcher } : {},
6132
- hooks: [{
6133
- type: "command",
6134
- command: finalCommand
6135
- }]
6136
- };
6137
- }))
6245
+ ...translatedEntries
6138
6246
  ];
6139
6247
  }
6140
6248
  await writeJson("hooks/hooks.json", { hooks });
6141
6249
  }
6250
+ async function mapClaudeHookEntry(args2) {
6251
+ const {
6252
+ entry,
6253
+ mappedEvent,
6254
+ options,
6255
+ shouldWrapClaudeHookCommands,
6256
+ usesPlatformManagedAuth,
6257
+ writeFile: writeFile3,
6258
+ nextWrapperIndex
6259
+ } = args2;
6260
+ const entryType = entry.type ?? "command";
6261
+ if (entryType === "prompt" && !isHookFieldPreserved("claude-code", "prompt", mappedEvent)) {
6262
+ return null;
6263
+ }
6264
+ if (entryType === "command") {
6265
+ if (!entry.command) return null;
6266
+ if (usesPlatformManagedAuth && entry.command.includes("check-env.sh")) {
6267
+ return null;
6268
+ }
6269
+ const command2 = entry.command.replace("${PLUGIN_ROOT}", `\${${options.pluginRootVar}}`);
6270
+ const finalCommand = shouldWrapClaudeHookCommands ? await (async () => {
6271
+ const relativePath = `hooks/pluxx-hook-command-${nextWrapperIndex()}.sh`;
6272
+ await writeFile3(relativePath, buildHookCommandWrapperScript(command2, options.pluginRootVar, "CLAUDE_ENV_FILE"));
6273
+ return `bash "\${${options.pluginRootVar}}/${relativePath}"`;
6274
+ })() : command2;
6275
+ return {
6276
+ ...entry.matcher !== void 0 ? { matcher: entry.matcher } : {},
6277
+ hooks: [{
6278
+ type: "command",
6279
+ command: finalCommand,
6280
+ ...entry.if !== void 0 ? { if: entry.if } : {},
6281
+ ...entry.timeout !== void 0 ? { timeout: entry.timeout } : {},
6282
+ ...entry.async !== void 0 ? { async: entry.async } : {},
6283
+ ...entry.asyncRewake !== void 0 ? { asyncRewake: entry.asyncRewake } : {},
6284
+ ...entry.shell !== void 0 ? { shell: entry.shell } : {}
6285
+ }]
6286
+ };
6287
+ }
6288
+ if (entryType === "http") {
6289
+ if (!entry.url) return null;
6290
+ return {
6291
+ ...entry.matcher !== void 0 ? { matcher: entry.matcher } : {},
6292
+ hooks: [{
6293
+ type: "http",
6294
+ url: entry.url,
6295
+ ...entry.if !== void 0 ? { if: entry.if } : {},
6296
+ ...entry.timeout !== void 0 ? { timeout: entry.timeout } : {},
6297
+ ...entry.headers !== void 0 ? { headers: entry.headers } : {},
6298
+ ...entry.allowedEnvVars !== void 0 ? { allowedEnvVars: entry.allowedEnvVars } : {}
6299
+ }]
6300
+ };
6301
+ }
6302
+ if (entryType === "mcp_tool") {
6303
+ if (!entry.server || !entry.tool) return null;
6304
+ return {
6305
+ ...entry.matcher !== void 0 ? { matcher: entry.matcher } : {},
6306
+ hooks: [{
6307
+ type: "mcp_tool",
6308
+ server: entry.server,
6309
+ tool: entry.tool,
6310
+ ...entry.if !== void 0 ? { if: entry.if } : {},
6311
+ ...entry.input !== void 0 ? { input: entry.input } : {}
6312
+ }]
6313
+ };
6314
+ }
6315
+ if (entryType === "prompt" || entryType === "agent") {
6316
+ if (!entry.prompt) return null;
6317
+ return {
6318
+ ...entry.matcher !== void 0 ? { matcher: entry.matcher } : {},
6319
+ hooks: [{
6320
+ type: entryType,
6321
+ prompt: entry.prompt,
6322
+ ...entry.if !== void 0 ? { if: entry.if } : {},
6323
+ ...entry.model !== void 0 ? { model: entry.model } : {}
6324
+ }]
6325
+ };
6326
+ }
6327
+ return null;
6328
+ }
6142
6329
  async function writeInstructions(config, rootDir, options, writeFile3) {
6143
- if (!config.instructions) return;
6144
- const srcPath = resolve6(rootDir, config.instructions);
6145
- if (!existsSync6(srcPath)) return;
6146
- const content = await readTextFile(srcPath);
6330
+ const content = await readInstructionsContent(rootDir, config);
6331
+ if (!content) return;
6147
6332
  const titleSuffix = options.titleSuffix ?? "Plugin";
6148
- const instructions = [
6149
- `# ${config.brand?.displayName ?? config.name} ${titleSuffix}`,
6150
- "",
6151
- config.brand?.shortDescription ?? config.description,
6152
- "",
6153
- content
6154
- ].join("\n");
6333
+ const instructions = renderTitledInstructionsDocument(config, content, titleSuffix);
6155
6334
  await writeFile3(options.instructionsFile, instructions);
6156
6335
  }
6157
6336
  function defaultMapEventName(event) {
@@ -6159,14 +6338,14 @@ function defaultMapEventName(event) {
6159
6338
  }
6160
6339
 
6161
6340
  // src/generators/claude-code/index.ts
6162
- import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync4, readdirSync as readdirSync3, writeFileSync } from "fs";
6341
+ import { existsSync as existsSync8, mkdirSync as mkdirSync2, readFileSync as readFileSync5, readdirSync as readdirSync3, writeFileSync } from "fs";
6163
6342
  import { basename as basename2, join as join2 } from "path";
6164
6343
 
6165
6344
  // src/delegation.ts
6166
6345
  function getPortableDelegationProfile(frontmatter) {
6167
- const permission = asMap(frontmatter.permission);
6168
- const bash = asMap(permission?.bash);
6169
- const task = asMap(permission?.task);
6346
+ const permission = asMap2(frontmatter.permission);
6347
+ const bash = asMap2(permission?.bash);
6348
+ const task = asMap2(permission?.task);
6170
6349
  return {
6171
6350
  mode: typeof frontmatter.mode === "string" ? frontmatter.mode : void 0,
6172
6351
  hidden: frontmatter.hidden === true,
@@ -6196,14 +6375,14 @@ function buildDelegationBehaviorNotes(frontmatter) {
6196
6375
  }
6197
6376
  return notes;
6198
6377
  }
6199
- function asMap(value) {
6378
+ function asMap2(value) {
6200
6379
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
6201
6380
  return value;
6202
6381
  }
6203
6382
 
6204
6383
  // src/skills.ts
6205
- import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync3, statSync as statSync2 } from "fs";
6206
- import { relative as relative2, resolve as resolve7 } from "path";
6384
+ import { existsSync as existsSync7, readdirSync as readdirSync2, readFileSync as readFileSync4, statSync as statSync2 } from "fs";
6385
+ import { relative as relative2, resolve as resolve8 } from "path";
6207
6386
  function unquote(value) {
6208
6387
  const trimmed = value.trim();
6209
6388
  if (trimmed.length >= 2) {
@@ -6287,6 +6466,34 @@ function parseAllowedTools(hasValidFrontmatter, frontmatterLines, frontmatterFie
6287
6466
  }
6288
6467
  return tools;
6289
6468
  }
6469
+ function parseBooleanField(frontmatterFields, key) {
6470
+ const value = frontmatterFields.get(key)?.value.trim().toLowerCase();
6471
+ if (value === "true") return true;
6472
+ if (value === "false") return false;
6473
+ return void 0;
6474
+ }
6475
+ function parseInlineStringArray(rawValue) {
6476
+ const trimmed = rawValue.trim();
6477
+ if (!trimmed.startsWith("[") || !trimmed.endsWith("]")) return [];
6478
+ const inner = trimmed.slice(1, -1).trim();
6479
+ if (!inner) return [];
6480
+ return inner.split(",").map((part) => unquote(part).value.trim()).filter(Boolean);
6481
+ }
6482
+ function parseStringArrayField(frontmatterFields, key) {
6483
+ const rawValue = frontmatterFields.get(key)?.rawValue;
6484
+ if (!rawValue) return [];
6485
+ return parseInlineStringArray(rawValue);
6486
+ }
6487
+ function parseJsonField(frontmatterFields, key) {
6488
+ const rawValue = frontmatterFields.get(key)?.rawValue?.trim();
6489
+ if (!rawValue) return void 0;
6490
+ if (!(rawValue.startsWith("{") || rawValue.startsWith("["))) return void 0;
6491
+ try {
6492
+ return JSON.parse(rawValue);
6493
+ } catch {
6494
+ return void 0;
6495
+ }
6496
+ }
6290
6497
  function parseSkillMarkdown(content) {
6291
6498
  const {
6292
6499
  hasValidFrontmatter,
@@ -6301,7 +6508,19 @@ function parseSkillMarkdown(content) {
6301
6508
  body,
6302
6509
  name: frontmatterFields.get("name")?.value,
6303
6510
  description: frontmatterFields.get("description")?.value,
6511
+ whenToUse: frontmatterFields.get("when_to_use")?.value,
6512
+ argumentHint: frontmatterFields.get("argument-hint")?.value,
6513
+ arguments: parseStringArrayField(frontmatterFields, "arguments"),
6514
+ disableModelInvocation: parseBooleanField(frontmatterFields, "disable-model-invocation"),
6515
+ userInvocable: parseBooleanField(frontmatterFields, "user-invocable"),
6304
6516
  allowedTools: parseAllowedTools(hasValidFrontmatter, frontmatterLines, frontmatterFields),
6517
+ model: frontmatterFields.get("model")?.value,
6518
+ effort: frontmatterFields.get("effort")?.value,
6519
+ context: frontmatterFields.get("context")?.value,
6520
+ agent: frontmatterFields.get("agent")?.value,
6521
+ hooks: parseJsonField(frontmatterFields, "hooks"),
6522
+ paths: parseStringArrayField(frontmatterFields, "paths"),
6523
+ shell: frontmatterFields.get("shell")?.value,
6305
6524
  firstHeading: firstHeading2(body)
6306
6525
  };
6307
6526
  }
@@ -6310,7 +6529,7 @@ function walkSkillFiles(skillsDir) {
6310
6529
  const entries = readdirSync2(skillsDir);
6311
6530
  const files = [];
6312
6531
  for (const entry of entries) {
6313
- const fullPath = resolve7(skillsDir, entry);
6532
+ const fullPath = resolve8(skillsDir, entry);
6314
6533
  const stat = statSync2(fullPath);
6315
6534
  if (stat.isDirectory()) {
6316
6535
  files.push(...walkSkillFiles(fullPath));
@@ -6323,7 +6542,7 @@ function walkSkillFiles(skillsDir) {
6323
6542
  return files;
6324
6543
  }
6325
6544
  function readSkillMarkdownFile(filePath) {
6326
- return parseSkillMarkdown(readFileSync3(filePath, "utf-8"));
6545
+ return parseSkillMarkdown(readFileSync4(filePath, "utf-8"));
6327
6546
  }
6328
6547
  function readCanonicalSkillFiles(skillsDir) {
6329
6548
  if (!skillsDir || !existsSync7(skillsDir)) return [];
@@ -6374,7 +6593,7 @@ var ClaudeCodeGenerator = class extends Generator {
6374
6593
  for (const skill of collidingSkills) {
6375
6594
  const outputPath = join2(this.outDir, "skills", skill.dirName, "SKILL.md");
6376
6595
  if (!existsSync8(outputPath)) continue;
6377
- const current = readFileSync4(outputPath, "utf-8");
6596
+ const current = readFileSync5(outputPath, "utf-8");
6378
6597
  const hiddenName = buildHiddenSkillName(skill.effectiveName);
6379
6598
  const rewritten = rewriteClaudeSkillVisibility(current, {
6380
6599
  nameOverride: hiddenName,
@@ -6388,7 +6607,7 @@ var ClaudeCodeGenerator = class extends Generator {
6388
6607
  if (collidingSkills.some((entry) => entry.dirName === skill.dirName)) continue;
6389
6608
  const outputPath = join2(this.outDir, "skills", skill.dirName, "SKILL.md");
6390
6609
  if (!existsSync8(outputPath)) continue;
6391
- const current = readFileSync4(outputPath, "utf-8");
6610
+ const current = readFileSync5(outputPath, "utf-8");
6392
6611
  const rewritten = rewriteClaudeSkillVisibility(current, {
6393
6612
  userInvocable: false
6394
6613
  });
@@ -6404,21 +6623,20 @@ var ClaudeCodeGenerator = class extends Generator {
6404
6623
  if (agents.length === 0) return;
6405
6624
  mkdirSync2(join2(this.outDir, "agents"), { recursive: true });
6406
6625
  for (const agent of agents) {
6626
+ const metadata = getCanonicalAgentMetadata(agent);
6407
6627
  const frontmatter = [
6408
6628
  "---",
6409
- `name: ${JSON.stringify(agent.name)}`,
6410
- `description: ${JSON.stringify(agent.description ?? `${agent.name} specialist.`)}`
6629
+ `name: ${JSON.stringify(metadata.name)}`,
6630
+ `description: ${JSON.stringify(metadata.description)}`
6411
6631
  ];
6412
- if (typeof agent.frontmatter.model === "string" && agent.frontmatter.model) {
6413
- frontmatter.push(`model: ${JSON.stringify(agent.frontmatter.model)}`);
6632
+ if (metadata.model) {
6633
+ frontmatter.push(`model: ${JSON.stringify(metadata.model)}`);
6414
6634
  }
6415
- const effort = typeof agent.frontmatter.model_reasoning_effort === "string" && agent.frontmatter.model_reasoning_effort ? agent.frontmatter.model_reasoning_effort : typeof agent.frontmatter.effort === "string" && agent.frontmatter.effort ? agent.frontmatter.effort : void 0;
6416
- if (effort) {
6417
- frontmatter.push(`effort: ${JSON.stringify(effort)}`);
6635
+ if (metadata.modelReasoningEffort) {
6636
+ frontmatter.push(`effort: ${JSON.stringify(metadata.modelReasoningEffort)}`);
6418
6637
  }
6419
- const maxTurns = typeof agent.frontmatter.maxTurns === "number" ? agent.frontmatter.maxTurns : typeof agent.frontmatter.steps === "number" ? agent.frontmatter.steps : typeof agent.frontmatter.maxSteps === "number" ? agent.frontmatter.maxSteps : void 0;
6420
- if (typeof maxTurns === "number") {
6421
- frontmatter.push(`maxTurns: ${maxTurns}`);
6638
+ if (typeof metadata.steps === "number") {
6639
+ frontmatter.push(`maxTurns: ${metadata.steps}`);
6422
6640
  }
6423
6641
  const claudeTools = selectClaudeToolsField(agent.frontmatter);
6424
6642
  if (claudeTools) {
@@ -6428,20 +6646,20 @@ var ClaudeCodeGenerator = class extends Generator {
6428
6646
  if (disallowedTools.length > 0) {
6429
6647
  frontmatter.push(`disallowedTools: ${disallowedTools.join(", ")}`);
6430
6648
  }
6431
- if (typeof agent.frontmatter.skills === "string" && agent.frontmatter.skills.trim()) {
6432
- frontmatter.push(`skills: ${agent.frontmatter.skills}`);
6649
+ if (metadata.skills) {
6650
+ frontmatter.push(`skills: ${metadata.skills}`);
6433
6651
  }
6434
- if (typeof agent.frontmatter.memory === "string" && agent.frontmatter.memory.trim()) {
6435
- frontmatter.push(`memory: ${JSON.stringify(agent.frontmatter.memory)}`);
6652
+ if (metadata.memory) {
6653
+ frontmatter.push(`memory: ${JSON.stringify(metadata.memory)}`);
6436
6654
  }
6437
- if (typeof agent.frontmatter.background === "boolean") {
6438
- frontmatter.push(`background: ${agent.frontmatter.background}`);
6655
+ if (typeof metadata.background === "boolean") {
6656
+ frontmatter.push(`background: ${metadata.background}`);
6439
6657
  }
6440
- if (typeof agent.frontmatter.isolation === "string" && agent.frontmatter.isolation.trim()) {
6441
- frontmatter.push(`isolation: ${JSON.stringify(agent.frontmatter.isolation)}`);
6658
+ if (metadata.isolation) {
6659
+ frontmatter.push(`isolation: ${JSON.stringify(metadata.isolation)}`);
6442
6660
  }
6443
- if (typeof agent.frontmatter.color === "string" && agent.frontmatter.color.trim()) {
6444
- frontmatter.push(`color: ${JSON.stringify(agent.frontmatter.color)}`);
6661
+ if (metadata.color) {
6662
+ frontmatter.push(`color: ${JSON.stringify(metadata.color)}`);
6445
6663
  }
6446
6664
  frontmatter.push("---");
6447
6665
  const delegationNotes = buildDelegationBehaviorNotes(agent.frontmatter);
@@ -6451,7 +6669,7 @@ var ClaudeCodeGenerator = class extends Generator {
6451
6669
  ...delegationNotes.map((note) => `- ${note}`),
6452
6670
  ""
6453
6671
  ] : [],
6454
- agent.body
6672
+ metadata.body
6455
6673
  ].filter(Boolean);
6456
6674
  const outputPath = join2(this.outDir, "agents", `${agent.fileStem}.md`);
6457
6675
  writeFileSync(outputPath, `${frontmatter.join("\n")}
@@ -6505,7 +6723,7 @@ function collectWrappedSkillNames(commandsRoot) {
6505
6723
  const wrappedSkills = /* @__PURE__ */ new Set();
6506
6724
  for (const entry of readdirSync3(commandsRoot, { withFileTypes: true })) {
6507
6725
  if (!entry.isFile() || !entry.name.toLowerCase().endsWith(".md")) continue;
6508
- const content = readFileSync4(join2(commandsRoot, entry.name), "utf-8");
6726
+ const content = readFileSync5(join2(commandsRoot, entry.name), "utf-8");
6509
6727
  for (const match of content.matchAll(/Use the `([^`]+)` skill\./g)) {
6510
6728
  const skillName = match[1]?.trim();
6511
6729
  if (skillName) wrappedSkills.add(skillName);
@@ -6552,9 +6770,9 @@ function rewriteClaudeSkillVisibility(content, options) {
6552
6770
  }
6553
6771
  function buildClaudeDisallowedTools(frontmatter) {
6554
6772
  const tools = /* @__PURE__ */ new Set();
6555
- const permission = asMap2(frontmatter.permission);
6556
- const bash = asMap2(permission?.bash);
6557
- const legacyTools = asMap2(frontmatter.tools);
6773
+ const permission = asMap3(frontmatter.permission);
6774
+ const bash = asMap3(permission?.bash);
6775
+ const legacyTools = asMap3(frontmatter.tools);
6558
6776
  if (permission?.edit === "deny") {
6559
6777
  tools.add("Write");
6560
6778
  tools.add("Edit");
@@ -6588,13 +6806,12 @@ function selectClaudeToolsField(frontmatter) {
6588
6806
  }
6589
6807
  return tools.join(", ");
6590
6808
  }
6591
- function asMap2(value) {
6809
+ function asMap3(value) {
6592
6810
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
6593
6811
  return value;
6594
6812
  }
6595
6813
 
6596
6814
  // src/generators/cursor/index.ts
6597
- import { existsSync as existsSync9 } from "fs";
6598
6815
  var CursorGenerator = class extends Generator {
6599
6816
  platform = "cursor";
6600
6817
  async generate() {
@@ -6639,6 +6856,7 @@ var CursorGenerator = class extends Generator {
6639
6856
  if (!this.config.hooks && !permissionScript && !readinessPlan.hasReadiness) return;
6640
6857
  const usesPlatformManagedAuth = this.config.platforms?.cursor?.mcpAuth === "platform";
6641
6858
  const hooks = {};
6859
+ let nextWrapperIndex = 0;
6642
6860
  if (readinessPlan.hasReadiness && this.config.readiness) {
6643
6861
  await this.writeFile("hooks/pluxx-readiness.mjs", buildGeneratedReadinessScript(this.config.readiness));
6644
6862
  for (const binding of getEnabledRuntimeReadinessBindings(readinessCapability, readinessPlan)) {
@@ -6690,25 +6908,36 @@ var CursorGenerator = class extends Generator {
6690
6908
  return true;
6691
6909
  });
6692
6910
  if (filteredEntries.length === 0) continue;
6911
+ const mappedEntries = [];
6912
+ for (const entry of filteredEntries) {
6913
+ const hookDef = {};
6914
+ if (entry.type === "prompt") {
6915
+ hookDef.type = "prompt";
6916
+ hookDef.prompt = entry.prompt;
6917
+ if (entry.model) hookDef.model = entry.model;
6918
+ } else if (entry.command) {
6919
+ nextWrapperIndex += 1;
6920
+ const relativePath = `hooks/pluxx-hook-command-${nextWrapperIndex}.sh`;
6921
+ await this.writeFile(
6922
+ relativePath,
6923
+ buildHookCommandWrapperScript(entry.command.replace("${PLUGIN_ROOT}", "."), "CURSOR_PLUGIN_ROOT")
6924
+ );
6925
+ hookDef.command = `bash ./${relativePath}`;
6926
+ }
6927
+ if (entry.timeout) hookDef.timeout = entry.timeout;
6928
+ if (entry.matcher) hookDef.matcher = entry.matcher;
6929
+ if (entry.failClosed) hookDef.failClosed = entry.failClosed;
6930
+ if (entry.loop_limit !== void 0 && getHookFieldSupportedEvents("cursor", "loop_limit").includes(event)) {
6931
+ hookDef.loop_limit = entry.loop_limit;
6932
+ }
6933
+ if (Object.keys(hookDef).length > 0) {
6934
+ mappedEntries.push(hookDef);
6935
+ }
6936
+ }
6937
+ if (mappedEntries.length === 0) continue;
6693
6938
  hooks[event] = [
6694
6939
  ...hooks[event] ?? [],
6695
- ...filteredEntries.map((entry) => {
6696
- const hookDef = {};
6697
- if (entry.type === "prompt") {
6698
- hookDef.type = "prompt";
6699
- hookDef.prompt = entry.prompt;
6700
- if (entry.model) hookDef.model = entry.model;
6701
- } else if (entry.command) {
6702
- hookDef.command = entry.command.replace("${PLUGIN_ROOT}", ".");
6703
- }
6704
- if (entry.timeout) hookDef.timeout = entry.timeout;
6705
- if (entry.matcher) hookDef.matcher = entry.matcher;
6706
- if (entry.failClosed) hookDef.failClosed = entry.failClosed;
6707
- if (entry.loop_limit !== void 0 && getHookFieldSupportedEvents("cursor", "loop_limit").includes(event)) {
6708
- hookDef.loop_limit = entry.loop_limit;
6709
- }
6710
- return hookDef;
6711
- })
6940
+ ...mappedEntries
6712
6941
  ];
6713
6942
  }
6714
6943
  await this.writeJson("hooks/hooks.json", { version: 1, hooks });
@@ -6741,10 +6970,8 @@ var CursorGenerator = class extends Generator {
6741
6970
  }
6742
6971
  }
6743
6972
  async generateAgentsMd() {
6744
- if (!this.config.instructions) return;
6745
- const srcPath = this.resolveConfigPath(this.config.instructions, "instructions");
6746
- if (!existsSync9(srcPath)) return;
6747
- const content = await readTextFile(srcPath);
6973
+ const content = await readInstructionsContent(this.rootDir, this.config);
6974
+ if (!content) return;
6748
6975
  await this.writeFile("AGENTS.md", content);
6749
6976
  }
6750
6977
  async generateAgents() {
@@ -6753,13 +6980,14 @@ var CursorGenerator = class extends Generator {
6753
6980
  const agents = readCanonicalAgentFiles(agentsDir);
6754
6981
  if (agents.length === 0) return;
6755
6982
  for (const agent of agents) {
6983
+ const metadata = getCanonicalAgentMetadata(agent);
6756
6984
  const frontmatter = [
6757
6985
  "---",
6758
- `name: ${JSON.stringify(agent.name)}`,
6759
- `description: ${JSON.stringify(agent.description ?? `${agent.name} specialist.`)}`
6986
+ `name: ${JSON.stringify(metadata.name)}`,
6987
+ `description: ${JSON.stringify(metadata.description)}`
6760
6988
  ];
6761
- if (typeof agent.frontmatter.model === "string" && agent.frontmatter.model) {
6762
- frontmatter.push(`model: ${JSON.stringify(agent.frontmatter.model)}`);
6989
+ if (metadata.model) {
6990
+ frontmatter.push(`model: ${JSON.stringify(metadata.model)}`);
6763
6991
  }
6764
6992
  frontmatter.push("---");
6765
6993
  const translatedNotes = buildCursorAgentTranslationNotes(agent.frontmatter);
@@ -6784,12 +7012,11 @@ function buildCursorAgentTranslationNotes(frontmatter) {
6784
7012
  }
6785
7013
 
6786
7014
  // src/generators/codex/index.ts
6787
- import { existsSync as existsSync11 } from "fs";
6788
7015
  import { relative as relative4 } from "path";
6789
7016
 
6790
7017
  // src/commands.ts
6791
- import { existsSync as existsSync10, readdirSync as readdirSync4, readFileSync as readFileSync5, statSync as statSync3 } from "fs";
6792
- import { relative as relative3, resolve as resolve8 } from "path";
7018
+ import { existsSync as existsSync9, readdirSync as readdirSync4, readFileSync as readFileSync6, statSync as statSync3 } from "fs";
7019
+ import { relative as relative3, resolve as resolve9 } from "path";
6793
7020
  function firstHeading3(content) {
6794
7021
  const lines = content.split(/\r?\n/);
6795
7022
  for (const line of lines) {
@@ -6861,7 +7088,7 @@ function walkMarkdownFiles2(dir) {
6861
7088
  const entries = readdirSync4(dir);
6862
7089
  const files = [];
6863
7090
  for (const entry of entries) {
6864
- const fullPath = resolve8(dir, entry);
7091
+ const fullPath = resolve9(dir, entry);
6865
7092
  const stat = statSync3(fullPath);
6866
7093
  if (stat.isDirectory()) {
6867
7094
  files.push(...walkMarkdownFiles2(fullPath));
@@ -6874,9 +7101,9 @@ function walkMarkdownFiles2(dir) {
6874
7101
  return files;
6875
7102
  }
6876
7103
  function readCanonicalCommandFiles(commandsDir) {
6877
- if (!commandsDir || !existsSync10(commandsDir)) return [];
7104
+ if (!commandsDir || !existsSync9(commandsDir)) return [];
6878
7105
  return walkMarkdownFiles2(commandsDir).sort((a, b) => a.localeCompare(b)).map((filePath) => {
6879
- const content = readFileSync5(filePath, "utf-8");
7106
+ const content = readFileSync6(filePath, "utf-8");
6880
7107
  const { frontmatterLines, body } = splitMarkdownFrontmatter3(content);
6881
7108
  const relativeStem = relative3(commandsDir, filePath).replace(/\\/g, "/").replace(/\.md$/i, "");
6882
7109
  const commandId = relativeStem.replace(/\//g, "-").toLowerCase();
@@ -6895,6 +7122,18 @@ function readCanonicalCommandFiles(commandsDir) {
6895
7122
  };
6896
7123
  });
6897
7124
  }
7125
+ function getCanonicalCommandMetadata(command2) {
7126
+ return {
7127
+ commandId: command2.commandId,
7128
+ title: command2.title,
7129
+ ...command2.description ? { description: command2.description } : {},
7130
+ ...command2.argumentHint ? { argumentHint: command2.argumentHint } : {},
7131
+ ...command2.agent ? { agent: command2.agent } : {},
7132
+ ...typeof command2.subtask === "boolean" ? { subtask: command2.subtask } : {},
7133
+ ...command2.model ? { model: command2.model } : {},
7134
+ template: command2.body
7135
+ };
7136
+ }
6898
7137
 
6899
7138
  // src/generators/codex/index.ts
6900
7139
  var CodexGenerator = class extends Generator {
@@ -6958,6 +7197,9 @@ var CodexGenerator = class extends Generator {
6958
7197
  if (this.config.keywords) manifest.keywords = this.config.keywords;
6959
7198
  manifest.skills = "./skills/";
6960
7199
  if (this.config.mcp) manifest.mcpServers = "./.mcp.json";
7200
+ if (this.config.hooks || getRuntimeReadinessPlan(this.config.readiness).hasReadiness) {
7201
+ manifest.hooks = "./hooks/hooks.json";
7202
+ }
6961
7203
  if (this.config.brand) {
6962
7204
  const iface = {
6963
7205
  displayName: this.config.brand.displayName,
@@ -7018,11 +7260,9 @@ var CodexGenerator = class extends Generator {
7018
7260
  }
7019
7261
  async generateAgentsMd() {
7020
7262
  const sections = [];
7021
- if (this.config.instructions) {
7022
- const srcPath = this.resolveConfigPath(this.config.instructions, "instructions");
7023
- if (existsSync11(srcPath)) {
7024
- sections.push((await readTextFile(srcPath)).trim());
7025
- }
7263
+ const instructions = await readInstructionsContent(this.rootDir, this.config);
7264
+ if (instructions?.trim()) {
7265
+ sections.push(instructions.trim());
7026
7266
  }
7027
7267
  const commandsSection = this.buildCodexCommandRoutingSection();
7028
7268
  if (commandsSection) {
@@ -7037,19 +7277,20 @@ var CodexGenerator = class extends Generator {
7037
7277
  const agents = readCanonicalAgentFiles(agentsDir);
7038
7278
  if (agents.length === 0) return;
7039
7279
  for (const agent of agents) {
7280
+ const metadata = getCanonicalAgentMetadata(agent);
7040
7281
  const relativePath = relative4(agentsDir, agent.filePath).replace(/\\/g, "/").replace(/\.md$/i, ".toml");
7041
7282
  const lines = [
7042
- `name = ${JSON.stringify(agent.name)}`,
7043
- `description = ${JSON.stringify(agent.description ?? `${agent.name} specialist.`)}`
7283
+ `name = ${JSON.stringify(metadata.name)}`,
7284
+ `description = ${JSON.stringify(metadata.description)}`
7044
7285
  ];
7045
- if (typeof agent.frontmatter.model === "string" && agent.frontmatter.model) {
7046
- lines.push(`model = ${JSON.stringify(agent.frontmatter.model)}`);
7286
+ if (metadata.model) {
7287
+ lines.push(`model = ${JSON.stringify(metadata.model)}`);
7047
7288
  }
7048
- if (typeof agent.frontmatter.model_reasoning_effort === "string" && agent.frontmatter.model_reasoning_effort) {
7049
- lines.push(`model_reasoning_effort = ${JSON.stringify(agent.frontmatter.model_reasoning_effort)}`);
7289
+ if (metadata.modelReasoningEffort) {
7290
+ lines.push(`model_reasoning_effort = ${JSON.stringify(metadata.modelReasoningEffort)}`);
7050
7291
  }
7051
- if (typeof agent.frontmatter.sandbox_mode === "string" && agent.frontmatter.sandbox_mode) {
7052
- lines.push(`sandbox_mode = ${JSON.stringify(agent.frontmatter.sandbox_mode)}`);
7292
+ if (metadata.sandboxMode) {
7293
+ lines.push(`sandbox_mode = ${JSON.stringify(metadata.sandboxMode)}`);
7053
7294
  }
7054
7295
  const delegationNotes = buildDelegationBehaviorNotes(agent.frontmatter);
7055
7296
  const developerInstructions = [
@@ -7058,7 +7299,7 @@ var CodexGenerator = class extends Generator {
7058
7299
  ...delegationNotes.map((note) => `- ${note}`),
7059
7300
  ""
7060
7301
  ] : [],
7061
- agent.body
7302
+ metadata.body
7062
7303
  ].filter(Boolean).join("\n");
7063
7304
  if (developerInstructions) {
7064
7305
  lines.push('developer_instructions = """');
@@ -7075,6 +7316,7 @@ var CodexGenerator = class extends Generator {
7075
7316
  if (!this.config.hooks && !readinessPlan.hasReadiness) return;
7076
7317
  const hooks = {};
7077
7318
  const unsupported = [];
7319
+ let nextWrapperIndex = 0;
7078
7320
  if (readinessPlan.hasReadiness && this.config.readiness) {
7079
7321
  await this.writeFile(".codex/pluxx-readiness.mjs", buildGeneratedReadinessScript(this.config.readiness));
7080
7322
  for (const binding of getEnabledRuntimeReadinessBindings(readinessCapability, readinessPlan)) {
@@ -7090,12 +7332,22 @@ var CodexGenerator = class extends Generator {
7090
7332
  for (const [event, entries] of Object.entries(this.config.hooks ?? {})) {
7091
7333
  if (!entries || entries.length === 0) continue;
7092
7334
  const codexEvent = mapHookEventToPascalCase(event);
7093
- const mappedEntries = entries.filter((entry) => entry.type !== "prompt" && entry.command).map((entry) => ({
7094
- command: entry.command?.replace("${PLUGIN_ROOT}", "."),
7095
- ...entry.matcher !== void 0 ? { matcher: entry.matcher } : {},
7096
- ...entry.timeout ? { timeout: entry.timeout } : {},
7097
- ...entry.failClosed !== void 0 ? { failClosed: entry.failClosed } : {}
7098
- }));
7335
+ const mappedEntries = [];
7336
+ for (const entry of entries) {
7337
+ if (entry.type === "prompt" || !entry.command) continue;
7338
+ nextWrapperIndex += 1;
7339
+ const relativePath = `hooks/pluxx-hook-command-${nextWrapperIndex}.sh`;
7340
+ await this.writeFile(
7341
+ relativePath,
7342
+ buildHookCommandWrapperScript(entry.command.replace("${PLUGIN_ROOT}", "."), "CODEX_PLUGIN_ROOT")
7343
+ );
7344
+ mappedEntries.push({
7345
+ command: `bash ./${relativePath}`,
7346
+ ...entry.matcher !== void 0 ? { matcher: entry.matcher } : {},
7347
+ ...entry.timeout ? { timeout: entry.timeout } : {},
7348
+ ...entry.failClosed !== void 0 ? { failClosed: entry.failClosed } : {}
7349
+ });
7350
+ }
7099
7351
  if (mappedEntries.length === 0) continue;
7100
7352
  if (!isHookEventSupported("codex", codexEvent)) {
7101
7353
  unsupported.push({
@@ -7111,11 +7363,15 @@ var CodexGenerator = class extends Generator {
7111
7363
  ];
7112
7364
  }
7113
7365
  if (Object.keys(hooks).length === 0 && unsupported.length === 0) return;
7366
+ await this.writeJson("hooks/hooks.json", {
7367
+ version: 1,
7368
+ hooks
7369
+ });
7114
7370
  await this.writeJson(".codex/hooks.generated.json", {
7115
7371
  model: "pluxx.codex-hooks.v1",
7116
- enforcedByPluginBundle: false,
7372
+ enforcedByPluginBundle: true,
7117
7373
  featureFlag: "codex_hooks",
7118
- note: "Codex hook configuration lives outside the plugin bundle. Use this file as a generated mirror for <repo>/.codex/hooks.json or ~/.codex/hooks.json and enable codex_hooks in Codex.",
7374
+ note: "Codex hook configuration is bundled at hooks/hooks.json in the plugin. This companion mirror preserves the translated native event names and highlights any dropped events or fields; enable codex_hooks in Codex if your runtime still requires that feature gate.",
7119
7375
  hooks,
7120
7376
  ...unsupported.length > 0 ? { unsupported } : {}
7121
7377
  });
@@ -7133,7 +7389,7 @@ var CodexGenerator = class extends Generator {
7133
7389
  await this.writeJson(".codex/readiness.generated.json", {
7134
7390
  model: "pluxx.readiness.v1",
7135
7391
  enforcedByPluginBundle: readinessCapability.bundleEnforced,
7136
- note: `${getRuntimeReadinessExternalConfigNote()} Use this file together with .codex/hooks.generated.json when wiring readiness into Codex hook config.`,
7392
+ note: `${getRuntimeReadinessExternalConfigNote()} Use this file together with hooks/hooks.json (and .codex/hooks.generated.json when debugging translated event names) when wiring readiness into Codex hook config.`,
7137
7393
  dependencies: this.config.readiness.dependencies,
7138
7394
  gates: this.config.readiness.gates,
7139
7395
  translatedHooks: {
@@ -7152,13 +7408,19 @@ var CodexGenerator = class extends Generator {
7152
7408
  model: "pluxx.commands.v1",
7153
7409
  nativeSurface: "degraded-to-guidance",
7154
7410
  note: "Codex does not currently document plugin-packaged slash commands. Use these canonical command entries as workflow routing guidance alongside AGENTS.md.",
7155
- commands: commands.map((command2) => ({
7156
- id: command2.commandId,
7157
- title: command2.title,
7158
- ...command2.description ? { description: command2.description } : {},
7159
- ...command2.argumentHint ? { argumentHint: command2.argumentHint } : {},
7160
- template: command2.body
7161
- }))
7411
+ commands: commands.map((command2) => {
7412
+ const metadata = getCanonicalCommandMetadata(command2);
7413
+ return {
7414
+ id: metadata.commandId,
7415
+ title: metadata.title,
7416
+ ...metadata.description ? { description: metadata.description } : {},
7417
+ ...metadata.argumentHint ? { argumentHint: metadata.argumentHint } : {},
7418
+ ...metadata.agent ? { agent: metadata.agent } : {},
7419
+ ...typeof metadata.subtask === "boolean" ? { subtask: metadata.subtask } : {},
7420
+ ...metadata.model ? { model: metadata.model } : {},
7421
+ template: metadata.template
7422
+ };
7423
+ })
7162
7424
  });
7163
7425
  }
7164
7426
  buildCodexCommandRoutingSection() {
@@ -7171,15 +7433,18 @@ var CodexGenerator = class extends Generator {
7171
7433
  "",
7172
7434
  "This plugin defines canonical command entrypoints. Codex does not package them as native slash commands today, so route those requests through the matching workflow directly.",
7173
7435
  "",
7174
- ...commands.map((command2) => `- \`/${command2.commandId}\` - ${command2.description ?? command2.title}${command2.argumentHint ? ` (arguments: ${command2.argumentHint})` : ""}`)
7436
+ ...commands.map((command2) => {
7437
+ const metadata = getCanonicalCommandMetadata(command2);
7438
+ return `- \`/${metadata.commandId}\` - ${metadata.description ?? metadata.title}${metadata.argumentHint ? ` (arguments: ${metadata.argumentHint})` : ""}`;
7439
+ })
7175
7440
  ];
7176
7441
  return lines.join("\n");
7177
7442
  }
7178
7443
  };
7179
7444
 
7180
7445
  // src/generators/opencode/index.ts
7181
- import { existsSync as existsSync12, readdirSync as readdirSync5, readFileSync as readFileSync6, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
7182
- import { basename as basename3, resolve as resolve9 } from "path";
7446
+ import { existsSync as existsSync10, readdirSync as readdirSync5, readFileSync as readFileSync7, statSync as statSync4, writeFileSync as writeFileSync2 } from "fs";
7447
+ import { basename as basename3, resolve as resolve10 } from "path";
7183
7448
  var OpenCodeGenerator = class extends Generator {
7184
7449
  platform = "opencode";
7185
7450
  async generate() {
@@ -7346,9 +7611,21 @@ var OpenCodeGenerator = class extends Generator {
7346
7611
  ` * Generated by pluxx \u2014 do not edit manually.`,
7347
7612
  ` */`,
7348
7613
  `export const ${pluginName}: Plugin = async ({ project, client, $, directory }) => {`,
7614
+ ` const shellSingleQuote = (input: string): string => \`'\${String(input ?? "").replace(/'/g, \`'"'"'\`)}'\``,
7615
+ "",
7616
+ ` const buildHookShellCommand = (rawCommand: string): string => {`,
7617
+ ` const userEnv = loadUserConfig(directory).env ?? {}`,
7618
+ ` const exports = Object.entries(userEnv)`,
7619
+ ` .filter(([key]) => /^[A-Za-z_][A-Za-z0-9_]*$/.test(key))`,
7620
+ ` .map(([key, value]) => \`export \${key}=\${shellSingleQuote(String(value))}\`)`,
7621
+ ` .join("; ")`,
7622
+ ` const command = rawCommand.replaceAll("\${PLUGIN_ROOT}", directory)`,
7623
+ ` return exports ? \`\${exports}; \${command}\` : command`,
7624
+ ` }`,
7625
+ "",
7349
7626
  ` const runHook = async (hook: GeneratedHook, context: Record<string, string>): Promise<void> => {`,
7350
7627
  ` try {`,
7351
- ` const command = hook.command.replaceAll("\${PLUGIN_ROOT}", directory)`,
7628
+ ` const command = buildHookShellCommand(hook.command)`,
7352
7629
  ` const execution = $\`bash -lc \${command}\``,
7353
7630
  ` if (hook.timeout) {`,
7354
7631
  ` await Promise.race([`,
@@ -7383,8 +7660,6 @@ var OpenCodeGenerator = class extends Generator {
7383
7660
  ` }`,
7384
7661
  ` }`,
7385
7662
  "",
7386
- ` const shellSingleQuote = (input: string): string => \`'\${String(input ?? "").replace(/'/g, \`'"'"'\`)}'\``,
7387
- "",
7388
7663
  ` const runReadiness = async (mode: string, event: Record<string, unknown>): Promise<void> => {`,
7389
7664
  ` if (!READINESS_SCRIPT) return`,
7390
7665
  ` const payload = Buffer.from(JSON.stringify(event ?? {}), "utf-8").toString("base64")`,
@@ -7531,13 +7806,14 @@ var OpenCodeGenerator = class extends Generator {
7531
7806
  const commands = readCanonicalCommandFiles(commandsDir);
7532
7807
  const output = {};
7533
7808
  for (const command2 of commands) {
7534
- output[command2.commandId] = {
7535
- template: command2.body,
7536
- ...command2.description ? { description: command2.description } : {},
7537
- ...command2.argumentHint ? { argumentHint: command2.argumentHint } : {},
7538
- ...command2.agent ? { agent: command2.agent } : {},
7539
- ...typeof command2.subtask === "boolean" ? { subtask: command2.subtask } : {},
7540
- ...command2.model ? { model: command2.model } : {}
7809
+ const metadata = getCanonicalCommandMetadata(command2);
7810
+ output[metadata.commandId] = {
7811
+ template: metadata.template,
7812
+ ...metadata.description ? { description: metadata.description } : {},
7813
+ ...metadata.argumentHint ? { argumentHint: metadata.argumentHint } : {},
7814
+ ...metadata.agent ? { agent: metadata.agent } : {},
7815
+ ...typeof metadata.subtask === "boolean" ? { subtask: metadata.subtask } : {},
7816
+ ...metadata.model ? { model: metadata.model } : {}
7541
7817
  };
7542
7818
  }
7543
7819
  return output;
@@ -7548,41 +7824,36 @@ var OpenCodeGenerator = class extends Generator {
7548
7824
  const agents = readCanonicalAgentFiles(agentsDir);
7549
7825
  const output = {};
7550
7826
  for (const agent of agents) {
7827
+ const metadata = getCanonicalAgentMetadata(agent);
7551
7828
  const definition = {
7552
- description: agent.description ?? `${agent.name} specialist.`
7829
+ description: metadata.description
7553
7830
  };
7554
- if (agent.body) {
7555
- definition.prompt = agent.body;
7556
- }
7557
- if (typeof agent.frontmatter.mode === "string" && agent.frontmatter.mode) {
7558
- definition.mode = agent.frontmatter.mode;
7831
+ if (metadata.body) {
7832
+ definition.prompt = metadata.body;
7559
7833
  }
7560
- if (typeof agent.frontmatter.model === "string" && agent.frontmatter.model) {
7561
- definition.model = agent.frontmatter.model;
7834
+ if (metadata.mode) {
7835
+ definition.mode = metadata.mode;
7562
7836
  }
7563
- if (typeof agent.frontmatter.temperature === "number") {
7564
- definition.temperature = agent.frontmatter.temperature;
7837
+ if (metadata.model) {
7838
+ definition.model = metadata.model;
7565
7839
  }
7566
- if (typeof agent.frontmatter.steps === "number") {
7567
- definition.steps = agent.frontmatter.steps;
7840
+ if (typeof metadata.temperature === "number") {
7841
+ definition.temperature = metadata.temperature;
7568
7842
  }
7569
- if (typeof agent.frontmatter.maxSteps === "number" && definition.steps === void 0) {
7570
- definition.steps = agent.frontmatter.maxSteps;
7843
+ if (typeof metadata.steps === "number") {
7844
+ definition.steps = metadata.steps;
7571
7845
  }
7572
- if (typeof agent.frontmatter.disable === "boolean") {
7573
- definition.disable = agent.frontmatter.disable;
7846
+ if (typeof metadata.disabled === "boolean") {
7847
+ definition.disable = metadata.disabled;
7574
7848
  }
7575
- if (typeof agent.frontmatter.hidden === "boolean") {
7576
- definition.hidden = agent.frontmatter.hidden;
7849
+ if (metadata.hidden) {
7850
+ definition.hidden = metadata.hidden;
7577
7851
  }
7578
- if (typeof agent.frontmatter.color === "string" && agent.frontmatter.color) {
7579
- definition.color = agent.frontmatter.color;
7852
+ if (metadata.color) {
7853
+ definition.color = metadata.color;
7580
7854
  }
7581
- if (typeof agent.frontmatter.topP === "number") {
7582
- definition.topP = agent.frontmatter.topP;
7583
- }
7584
- if (typeof agent.frontmatter.top_p === "number" && definition.topP === void 0) {
7585
- definition.topP = agent.frontmatter.top_p;
7855
+ if (typeof metadata.topP === "number") {
7856
+ definition.topP = metadata.topP;
7586
7857
  }
7587
7858
  const legacyToolTranslation = translateLegacyOpenCodeTools(agent.frontmatter.tools);
7588
7859
  const permission = mergeOpenCodeMaps(
@@ -7596,15 +7867,12 @@ var OpenCodeGenerator = class extends Generator {
7596
7867
  if (tools) {
7597
7868
  definition.tools = tools;
7598
7869
  }
7599
- output[agent.name] = definition;
7870
+ output[metadata.name] = definition;
7600
7871
  }
7601
7872
  return output;
7602
7873
  }
7603
7874
  getInstructionsContent() {
7604
- if (!this.config.instructions) return null;
7605
- const instructionsPath = this.resolveConfigPath(this.config.instructions, "instructions");
7606
- if (!existsSync12(instructionsPath)) return null;
7607
- return readFileSync6(instructionsPath, "utf-8").trim();
7875
+ return readInstructionsContentSync(this.rootDir, this.config)?.trim() ?? null;
7608
7876
  }
7609
7877
  getOpenCodeHookPlan() {
7610
7878
  const plan = {
@@ -7664,7 +7932,7 @@ var OpenCodeGenerator = class extends Generator {
7664
7932
  const entries = readdirSync5(dir);
7665
7933
  const files = [];
7666
7934
  for (const entry of entries) {
7667
- const fullPath = resolve9(dir, entry);
7935
+ const fullPath = resolve10(dir, entry);
7668
7936
  const stat = statSync4(fullPath);
7669
7937
  if (stat.isDirectory()) {
7670
7938
  files.push(...this.walkFiles(fullPath));
@@ -7676,14 +7944,14 @@ var OpenCodeGenerator = class extends Generator {
7676
7944
  }
7677
7945
  rewriteOpenCodeSkillAgentMentions() {
7678
7946
  if (!this.config.agents || !this.config.skills) return;
7679
- const skillsDir = resolve9(this.outDir, "skills");
7680
- if (!existsSync12(skillsDir)) return;
7947
+ const skillsDir = resolve10(this.outDir, "skills");
7948
+ if (!existsSync10(skillsDir)) return;
7681
7949
  const agentsDir = this.resolveConfigPath(this.config.agents, "agents");
7682
7950
  const agentNames = readCanonicalAgentFiles(agentsDir).map((agent) => agent.name).filter(Boolean);
7683
7951
  if (agentNames.length === 0) return;
7684
7952
  for (const filePath of this.walkFiles(skillsDir)) {
7685
7953
  if (basename3(filePath) !== "SKILL.md") continue;
7686
- const source = readFileSync6(filePath, "utf-8");
7954
+ const source = readFileSync7(filePath, "utf-8");
7687
7955
  let rewritten = source;
7688
7956
  for (const agentName of agentNames) {
7689
7957
  const escaped = escapeRegExp(agentName);
@@ -7842,7 +8110,7 @@ var OpenHandsGenerator = class extends Generator {
7842
8110
  };
7843
8111
 
7844
8112
  // src/generators/warp/index.ts
7845
- import { existsSync as existsSync13 } from "fs";
8113
+ import { existsSync as existsSync11 } from "fs";
7846
8114
  var WarpGenerator = class extends Generator {
7847
8115
  platform = "warp";
7848
8116
  async generate() {
@@ -7857,14 +8125,14 @@ var WarpGenerator = class extends Generator {
7857
8125
  async generateAgentsMd() {
7858
8126
  if (!this.config.instructions) return;
7859
8127
  const srcPath = this.resolveConfigPath(this.config.instructions, "instructions");
7860
- if (!existsSync13(srcPath)) return;
8128
+ if (!existsSync11(srcPath)) return;
7861
8129
  const content = await readTextFile(srcPath);
7862
8130
  await this.writeFile("AGENTS.md", content);
7863
8131
  }
7864
8132
  };
7865
8133
 
7866
8134
  // src/generators/gemini-cli/index.ts
7867
- import { existsSync as existsSync14 } from "fs";
8135
+ import { existsSync as existsSync12 } from "fs";
7868
8136
  var GeminiCliGenerator = class extends Generator {
7869
8137
  platform = "gemini-cli";
7870
8138
  async generate() {
@@ -7912,7 +8180,7 @@ var GeminiCliGenerator = class extends Generator {
7912
8180
  async generateInstructions() {
7913
8181
  if (!this.config.instructions) return;
7914
8182
  const srcPath = this.resolveConfigPath(this.config.instructions, "instructions");
7915
- if (!existsSync14(srcPath)) return;
8183
+ if (!existsSync12(srcPath)) return;
7916
8184
  const content = await readTextFile(srcPath);
7917
8185
  const geminiMd = [
7918
8186
  `# ${this.config.brand?.displayName ?? this.config.name}`,
@@ -7926,7 +8194,7 @@ var GeminiCliGenerator = class extends Generator {
7926
8194
  };
7927
8195
 
7928
8196
  // src/generators/roo-code/index.ts
7929
- import { existsSync as existsSync15 } from "fs";
8197
+ import { existsSync as existsSync13 } from "fs";
7930
8198
  var RooCodeGenerator = class extends Generator {
7931
8199
  platform = "roo-code";
7932
8200
  async generate() {
@@ -7941,7 +8209,7 @@ var RooCodeGenerator = class extends Generator {
7941
8209
  async generateRules() {
7942
8210
  if (!this.config.instructions) return;
7943
8211
  const srcPath = this.resolveConfigPath(this.config.instructions, "instructions");
7944
- if (!existsSync15(srcPath)) return;
8212
+ if (!existsSync13(srcPath)) return;
7945
8213
  const content = await readTextFile(srcPath);
7946
8214
  await this.writeFile(".roorules", content);
7947
8215
  }
@@ -7952,7 +8220,7 @@ var RooCodeGenerator = class extends Generator {
7952
8220
  };
7953
8221
 
7954
8222
  // src/generators/cline/index.ts
7955
- import { existsSync as existsSync16 } from "fs";
8223
+ import { existsSync as existsSync14 } from "fs";
7956
8224
  var ClineGenerator = class extends Generator {
7957
8225
  platform = "cline";
7958
8226
  async generate() {
@@ -7967,7 +8235,7 @@ var ClineGenerator = class extends Generator {
7967
8235
  async generateRules() {
7968
8236
  if (!this.config.instructions) return;
7969
8237
  const srcPath = this.resolveConfigPath(this.config.instructions, "instructions");
7970
- if (!existsSync16(srcPath)) return;
8238
+ if (!existsSync14(srcPath)) return;
7971
8239
  const content = await readTextFile(srcPath);
7972
8240
  await this.writeFile(".clinerules", content);
7973
8241
  }
@@ -7978,7 +8246,7 @@ var ClineGenerator = class extends Generator {
7978
8246
  };
7979
8247
 
7980
8248
  // src/generators/amp/index.ts
7981
- import { existsSync as existsSync17 } from "fs";
8249
+ import { existsSync as existsSync15 } from "fs";
7982
8250
  var AmpGenerator = class extends Generator {
7983
8251
  platform = "amp";
7984
8252
  async generate() {
@@ -7994,7 +8262,7 @@ var AmpGenerator = class extends Generator {
7994
8262
  async generateAgentMd() {
7995
8263
  if (!this.config.instructions) return;
7996
8264
  const srcPath = this.resolveConfigPath(this.config.instructions, "instructions");
7997
- if (!existsSync17(srcPath)) return;
8265
+ if (!existsSync15(srcPath)) return;
7998
8266
  const content = await readTextFile(srcPath);
7999
8267
  const agentMd = [
8000
8268
  `# ${this.config.brand?.displayName ?? this.config.name}`,
@@ -8038,7 +8306,7 @@ var GENERATORS = {
8038
8306
  amp: AmpGenerator
8039
8307
  };
8040
8308
  function assertPathWithinRoot(rootDir, configPath, configKey) {
8041
- const resolvedPath = resolve10(rootDir, configPath);
8309
+ const resolvedPath = resolve11(rootDir, configPath);
8042
8310
  const rel = relative6(rootDir, resolvedPath);
8043
8311
  if (rel.startsWith("..")) {
8044
8312
  throw new Error(`${configKey} path "${configPath}" resolves outside the project root.`);
@@ -8073,9 +8341,9 @@ function validateConfiguredPaths(config, rootDir) {
8073
8341
  }
8074
8342
  async function build(config, rootDir, options = {}) {
8075
8343
  const targets = options.targets ?? config.targets;
8076
- const outDir = resolve10(rootDir, config.outDir);
8344
+ const outDir = resolve11(rootDir, config.outDir);
8077
8345
  const rel = relative6(rootDir, outDir);
8078
- if (rel.startsWith("..") || resolve10(outDir) === resolve10(rootDir)) {
8346
+ if (rel.startsWith("..") || resolve11(outDir) === resolve11(rootDir)) {
8079
8347
  throw new Error(
8080
8348
  `outDir "${config.outDir}" resolves outside the project root. Refusing to delete.`
8081
8349
  );
@@ -8096,15 +8364,15 @@ async function build(config, rootDir, options = {}) {
8096
8364
  }
8097
8365
 
8098
8366
  // src/cli/agent.ts
8099
- import { existsSync as existsSync23 } from "fs";
8367
+ import { existsSync as existsSync21 } from "fs";
8100
8368
  import { chmod, copyFile, mkdir as mkdir3, mkdtemp, readFile as readFile3, rm as rm2 } from "fs/promises";
8101
8369
  import { homedir, tmpdir as tmpdir2 } from "os";
8102
- import { relative as relative10, resolve as resolve16 } from "path";
8370
+ import { relative as relative10, resolve as resolve17 } from "path";
8103
8371
  import { spawn as spawn2 } from "child_process";
8104
8372
 
8105
8373
  // src/cli/lint.ts
8106
- import { existsSync as existsSync18, lstatSync, readdirSync as readdirSync6, readFileSync as readFileSync7 } from "fs";
8107
- import { resolve as resolve11, relative as relative7, basename as basename4, dirname as dirname3 } from "path";
8374
+ import { existsSync as existsSync16, lstatSync, readdirSync as readdirSync6, readFileSync as readFileSync8 } from "fs";
8375
+ import { resolve as resolve12, relative as relative7, basename as basename4, dirname as dirname3 } from "path";
8108
8376
 
8109
8377
  // src/validation/platform-rules.ts
8110
8378
  var STANDARD_SKILL_FRONTMATTER = [
@@ -8280,7 +8548,7 @@ var PLATFORM_LIMIT_POLICIES = {
8280
8548
  var PLATFORM_VALIDATION_RULES = {
8281
8549
  "claude-code": {
8282
8550
  platform: "claude-code",
8283
- summary: "Claude Code plugins use an optional manifest at .claude-plugin/plugin.json with auto-discovery for skills, commands, agents, hooks, MCP, marketplaces, and output styles.",
8551
+ summary: "Claude Code plugins use an optional manifest at .claude-plugin/plugin.json with auto-discovery for skills, commands, agents, hooks, MCP, marketplaces, output styles, and adjacent plugin assets like LSP, monitors, and themes.",
8284
8552
  limits: PLATFORM_LIMITS["claude-code"],
8285
8553
  limitPolicies: PLATFORM_LIMIT_POLICIES["claude-code"],
8286
8554
  skillDiscoveryDirs: [
@@ -8319,13 +8587,13 @@ var PLATFORM_VALIDATION_RULES = {
8319
8587
  hooks: {
8320
8588
  supported: true,
8321
8589
  files: ["hooks/hooks.json", ".claude-plugin/plugin.json", "~/.claude/settings.json", ".claude/settings.json", ".claude/settings.local.json"],
8322
- eventNames: ["SessionStart", "PreToolUse", "PostToolUse", "PermissionRequest", "TaskCreated", "TaskCompleted", "Stop", "Notification", "ConfigChange"],
8323
- notes: "Hook configs can be stored in hooks/hooks.json, inlined in plugin.json, added in settings files, or scoped through skill and agent frontmatter."
8590
+ eventNames: ["SessionStart", "Setup", "UserPromptSubmit", "UserPromptExpansion", "PreToolUse", "PermissionRequest", "PermissionDenied", "PostToolUse", "PostToolUseFailure", "PostToolBatch", "Notification", "SubagentStart", "SubagentStop", "TaskCreated", "TaskCompleted", "Stop", "StopFailure", "TeammateIdle", "InstructionsLoaded", "ConfigChange", "CwdChanged", "FileChanged", "WorktreeCreate", "WorktreeRemove", "PreCompact", "PostCompact", "Elicitation", "ElicitationResult", "SessionEnd"],
8591
+ notes: "Hook configs can be stored in hooks/hooks.json, inlined in plugin.json, added in settings files, or scoped through skill and agent frontmatter. Claude also documents a broader lifecycle event set than the older simplified Pluxx model."
8324
8592
  },
8325
8593
  instructions: {
8326
8594
  files: ["CLAUDE.md"],
8327
8595
  format: "markdown",
8328
- notes: "Claude keeps persistent instructions in CLAUDE.md and pushes longer procedures into skills."
8596
+ notes: "Claude keeps persistent instructions in CLAUDE.md and pushes longer procedures into skills. Related enforcement and distribution config also depends on managed, user, project, and local settings scopes."
8329
8597
  },
8330
8598
  sources: [
8331
8599
  { label: "Claude Code MCP docs", url: "https://code.claude.com/docs/en/mcp" },
@@ -8339,6 +8607,9 @@ var PLATFORM_VALIDATION_RULES = {
8339
8607
  { label: "Claude Code plugins reference", url: "https://code.claude.com/docs/en/plugins-reference" },
8340
8608
  { label: "Claude Code hooks guide", url: "https://code.claude.com/docs/en/hooks-guide" },
8341
8609
  { label: "Claude Code hooks docs", url: "https://code.claude.com/docs/en/hooks" },
8610
+ { label: "Claude Code settings docs", url: "https://code.claude.com/docs/en/settings" },
8611
+ { label: "Claude Code permissions docs", url: "https://code.claude.com/docs/en/permissions" },
8612
+ { label: "Claude Code memory docs", url: "https://code.claude.com/docs/en/memory" },
8342
8613
  { label: "Claude Code skills docs", url: "https://code.claude.com/docs/en/skills" },
8343
8614
  { label: "Claude Code sub-agents docs", url: "https://code.claude.com/docs/en/sub-agents" },
8344
8615
  { label: "Claude Code env vars docs", url: "https://code.claude.com/docs/en/env-vars" }
@@ -8377,13 +8648,13 @@ var PLATFORM_VALIDATION_RULES = {
8377
8648
  hooks: {
8378
8649
  supported: true,
8379
8650
  files: ["hooks/hooks.json", ".cursor/hooks.json", "~/.cursor/hooks.json"],
8380
- eventNames: ["sessionStart", "preToolUse", "postToolUse", "subagentStart", "subagentStop", "beforeShellExecution", "afterShellExecution"],
8381
- notes: "Cursor plugin hooks live under hooks/hooks.json; project and user hooks also exist separately and reload on save."
8651
+ eventNames: ["sessionStart", "sessionEnd", "preToolUse", "postToolUse", "postToolUseFailure", "subagentStart", "subagentStop", "beforeShellExecution", "afterShellExecution", "beforeMCPExecution", "afterMCPExecution", "beforeReadFile", "afterFileEdit", "beforeSubmitPrompt", "preCompact", "stop", "afterAgentResponse", "afterAgentThought", "beforeTabFileRead", "afterTabFileEdit"],
8652
+ notes: "Cursor plugin hooks live under hooks/hooks.json; project and user hooks also exist separately and reload on save. The official event surface spans both Agent and Tab flows."
8382
8653
  },
8383
8654
  instructions: {
8384
8655
  files: ["rules/", "AGENTS.md"],
8385
8656
  format: "mdc + markdown",
8386
- notes: "rules/ is the plugin-native instruction surface. AGENTS.md remains useful as shared repo guidance. Cursor subagents use markdown files under .cursor/agents or ~/.cursor/agents (with .claude/.codex compatibility paths)."
8657
+ notes: "rules/ is the plugin-native instruction surface. AGENTS.md remains useful as shared repo guidance. Cursor subagents use markdown files under .cursor/agents or ~/.cursor/agents (with .claude/.codex compatibility paths). Cursor rules do not apply to Tab."
8387
8658
  },
8388
8659
  sources: [
8389
8660
  { label: "Cursor plugins overview", url: "https://cursor.com/docs/plugins" },
@@ -8403,7 +8674,7 @@ var PLATFORM_VALIDATION_RULES = {
8403
8674
  },
8404
8675
  "codex": {
8405
8676
  platform: "codex",
8406
- summary: "Codex plugins use .codex-plugin/plugin.json with skills, optional .mcp.json and .app.json, marketplace catalogs, cache installs, AGENTS.md instructions, and separate hook configuration.",
8677
+ summary: "Codex plugins use .codex-plugin/plugin.json with skills, optional .mcp.json and .app.json, marketplace catalogs, cache installs, AGENTS.md instructions, and native bundled hook support plus broader project or user hook config.",
8407
8678
  limits: PLATFORM_LIMITS["codex"],
8408
8679
  limitPolicies: PLATFORM_LIMIT_POLICIES["codex"],
8409
8680
  skillDiscoveryDirs: [
@@ -8431,9 +8702,9 @@ var PLATFORM_VALIDATION_RULES = {
8431
8702
  },
8432
8703
  hooks: {
8433
8704
  supported: true,
8434
- files: [".codex/hooks.json", "~/.codex/hooks.json"],
8705
+ files: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
8435
8706
  eventNames: ["SessionStart", "PreToolUse", "PermissionRequest", "PostToolUse", "UserPromptSubmit", "Stop"],
8436
- notes: "Codex documents hooks in project/user config, guarded by the codex_hooks feature flag; the current plugin build guide does not document plugin-packaged hooks."
8707
+ notes: "Codex documents hooks in project and user config, and the hooks docs still mention the codex_hooks feature flag/runtime caveat. The official hooks docs also cover plugin-bundled hooks, which Pluxx now emits at hooks/hooks.json."
8437
8708
  },
8438
8709
  instructions: {
8439
8710
  files: ["AGENTS.md", "AGENTS.override.md"],
@@ -8451,6 +8722,9 @@ var PLATFORM_VALIDATION_RULES = {
8451
8722
  { label: "Codex hooks docs", url: "https://developers.openai.com/codex/hooks" },
8452
8723
  { label: "Codex skills docs", url: "https://developers.openai.com/codex/skills" },
8453
8724
  { label: "Codex MCP docs", url: "https://developers.openai.com/codex/mcp" },
8725
+ { label: "Codex basic config docs", url: "https://developers.openai.com/codex/config-basic" },
8726
+ { label: "Codex agent approvals and security docs", url: "https://developers.openai.com/codex/agent-approvals-security" },
8727
+ { label: "Codex app commands docs", url: "https://developers.openai.com/codex/app/commands" },
8454
8728
  { label: "Codex AGENTS.md guide", url: "https://developers.openai.com/codex/guides/agents-md" },
8455
8729
  { label: "Codex subagents docs", url: "https://developers.openai.com/codex/subagents" },
8456
8730
  { label: "Codex subagents concept docs", url: "https://developers.openai.com/codex/concepts/subagents" },
@@ -8461,7 +8735,7 @@ var PLATFORM_VALIDATION_RULES = {
8461
8735
  },
8462
8736
  "opencode": {
8463
8737
  platform: "opencode",
8464
- summary: "OpenCode plugins are code-first JS or TS modules loaded from local plugin dirs or npm references in config, with native skills, commands, agents, MCP, and permission surfaces.",
8738
+ summary: "OpenCode plugins are code-first JS or TS modules loaded from local plugin dirs or npm references in config, with native skills, commands, agents, MCP, permission, custom-tool, and plugin-event surfaces.",
8465
8739
  limits: PLATFORM_LIMITS["opencode"],
8466
8740
  limitPolicies: PLATFORM_LIMIT_POLICIES["opencode"],
8467
8741
  skillDiscoveryDirs: [
@@ -8491,13 +8765,13 @@ var PLATFORM_VALIDATION_RULES = {
8491
8765
  hooks: {
8492
8766
  supported: true,
8493
8767
  files: ["plugin module (index.ts/index.js)"],
8494
- eventNames: [],
8495
- notes: "OpenCode hooks are plugin event handlers implemented in code, not a separate hooks.json file."
8768
+ eventNames: ["command.executed", "file.edited", "file.watcher.updated", "installation.updated", "lsp.client.diagnostics", "lsp.updated", "message.part.removed", "message.part.updated", "message.removed", "message.updated", "permission.asked", "permission.replied", "server.connected", "session.created", "session.compacted", "session.deleted", "session.diff", "session.error", "session.idle", "session.status", "session.updated", "todo.updated", "shell.env", "tool.execute.before", "tool.execute.after", "tui.prompt.append", "tui.command.execute", "tui.toast.show", "experimental.session.compacting"],
8769
+ notes: "OpenCode hooks are plugin event handlers implemented in code, not a separate hooks.json file. The official surface is an event bus rather than a small lifecycle-hook JSON schema."
8496
8770
  },
8497
8771
  instructions: {
8498
8772
  files: ["AGENTS.md", "CLAUDE.md", "opencode.json"],
8499
8773
  format: "markdown + json + code",
8500
- notes: "OpenCode supports AGENTS.md, CLAUDE.md fallback, config instructions, and plugin runtime instruction injection."
8774
+ notes: "OpenCode supports AGENTS.md, CLAUDE.md fallback, config instructions, and plugin runtime instruction injection. Config layering also includes remote config, managed config files, and macOS managed preferences."
8501
8775
  },
8502
8776
  sources: [
8503
8777
  { label: "OpenCode SDK docs", url: "https://opencode.ai/docs/sdk/" },
@@ -8868,8 +9142,8 @@ var CORE_FOUR_PRIMITIVE_CAPABILITIES = {
8868
9142
  },
8869
9143
  hooks: {
8870
9144
  mode: "translate",
8871
- nativeSurfaces: [".codex/hooks.json", "~/.codex/hooks.json"],
8872
- notes: "Hooks are native, but they currently live next to config layers rather than inside the documented plugin bundle structure."
9145
+ nativeSurfaces: ["hooks/hooks.json", ".codex/hooks.json", "~/.codex/hooks.json"],
9146
+ notes: "Hooks are native. Pluxx bundles translated Codex hooks in the plugin and still tracks the project/user config paths plus the codex_hooks feature-gate caveat."
8873
9147
  },
8874
9148
  permissions: {
8875
9149
  mode: "translate",
@@ -9136,12 +9410,15 @@ var CODEX_RULES = {
9136
9410
  };
9137
9411
  var CLAUDE_CODE_HOOK_EVENTS = [
9138
9412
  "SessionStart",
9413
+ "Setup",
9139
9414
  "PreToolUse",
9140
9415
  "PostToolUse",
9141
9416
  "UserPromptSubmit",
9417
+ "UserPromptExpansion",
9142
9418
  "PermissionRequest",
9143
9419
  "PermissionDenied",
9144
9420
  "PostToolUseFailure",
9421
+ "PostToolBatch",
9145
9422
  "Notification",
9146
9423
  "SubagentStart",
9147
9424
  "SubagentStop",
@@ -9162,7 +9439,7 @@ var CLAUDE_CODE_HOOK_EVENTS = [
9162
9439
  "ElicitationResult",
9163
9440
  "SessionEnd"
9164
9441
  ];
9165
- var CLAUDE_CODE_HOOK_TYPES = ["command", "http", "prompt", "agent"];
9442
+ var CLAUDE_CODE_HOOK_TYPES = ["command", "http", "mcp_tool", "prompt", "agent"];
9166
9443
  var AGENT_FORBIDDEN_FRONTMATTER = ["hooks", "mcpServers", "permissionMode"];
9167
9444
  var SEMVER_REGEX = /^\d+\.\d+\.\d+$/;
9168
9445
  var PLUGIN_NAME_KEBAB = /^[a-z0-9]+(-[a-z0-9]+)*$/;
@@ -9585,8 +9862,8 @@ function getBundledRuntimePayloadDirs(rootDir, config) {
9585
9862
  ].filter((entry) => typeof entry === "string" && entry.length > 0).map((entry) => resolveBundledPassthroughDir(rootDir, entry)).filter((entry) => Boolean(entry));
9586
9863
  }
9587
9864
  function resolveBundledPassthroughDir(rootDir, entry) {
9588
- const resolvedPath = resolve11(rootDir, entry);
9589
- if (!existsSync18(resolvedPath)) return null;
9865
+ const resolvedPath = resolve12(rootDir, entry);
9866
+ if (!existsSync16(resolvedPath)) return null;
9590
9867
  try {
9591
9868
  const stats = lstatSync(resolvedPath);
9592
9869
  if (!stats.isDirectory()) return null;
@@ -9601,8 +9878,8 @@ function findLocalStdioRuntimePaths(rootDir, server) {
9601
9878
  const candidates = [server.command, ...server.args ?? []];
9602
9879
  for (const candidate of candidates) {
9603
9880
  if (!isLikelyLocalRuntimePath(candidate)) continue;
9604
- const resolvedPath = resolve11(rootDir, candidate);
9605
- if (!existsSync18(resolvedPath)) continue;
9881
+ const resolvedPath = resolve12(rootDir, candidate);
9882
+ if (!existsSync16(resolvedPath)) continue;
9606
9883
  try {
9607
9884
  const stats = lstatSync(resolvedPath);
9608
9885
  const runtimeDir = stats.isDirectory() ? resolvedPath : dirname3(resolvedPath);
@@ -9718,11 +9995,16 @@ function lintInstructionsFileLimits(config, dir, issues) {
9718
9995
  for (const target of config.targets) {
9719
9996
  const limits = PLATFORM_LIMITS[target];
9720
9997
  if (limits.instructionsMaxBytes === null) continue;
9721
- const instructionsFiles = ["AGENTS.md", "CLAUDE.md", "INSTRUCTIONS.md"];
9998
+ const configuredInstructionsPath = resolveInstructionsPath(dir, config);
9999
+ const instructionsFiles = /* @__PURE__ */ new Set(["AGENTS.md", "CLAUDE.md", "INSTRUCTIONS.md"]);
10000
+ if (configuredInstructionsPath) {
10001
+ instructionsFiles.add(relative7(dir, configuredInstructionsPath).replace(/\\/g, "/"));
10002
+ }
9722
10003
  for (const file of instructionsFiles) {
9723
- const filePath = resolve11(dir, file);
9724
- if (!existsSync18(filePath)) continue;
9725
- const content = readFileSync7(filePath, "utf-8");
10004
+ const filePath = resolve12(dir, file);
10005
+ if (!existsSync16(filePath)) continue;
10006
+ const content = file === relative7(dir, configuredInstructionsPath ?? "").replace(/\\/g, "/") ? readInstructionsContentSync(dir, config) : readFileSync8(filePath, "utf-8");
10007
+ if (content === null) continue;
9726
10008
  const byteSize = Buffer.byteLength(content, "utf-8");
9727
10009
  if (byteSize > limits.instructionsMaxBytes) {
9728
10010
  pushIssue(issues, {
@@ -9742,9 +10024,9 @@ function lintRulesFileLimits(config, dir, issues) {
9742
10024
  if (limits.rulesMaxLines === null) continue;
9743
10025
  const rulesFiles = [".cursorrules", ".clinerules"];
9744
10026
  for (const file of rulesFiles) {
9745
- const filePath = resolve11(dir, file);
9746
- if (!existsSync18(filePath)) continue;
9747
- const content = readFileSync7(filePath, "utf-8");
10027
+ const filePath = resolve12(dir, file);
10028
+ if (!existsSync16(filePath)) continue;
10029
+ const content = readFileSync8(filePath, "utf-8");
9748
10030
  const lineCount = content.split(/\r?\n/).length;
9749
10031
  if (lineCount > limits.rulesMaxLines) {
9750
10032
  pushIssue(issues, {
@@ -9763,8 +10045,8 @@ function lintPluginDirectoryPlacement(dir, issues) {
9763
10045
  const nestedParents = [".claude-plugin", ".plugin"];
9764
10046
  for (const parent of nestedParents) {
9765
10047
  for (const subDir of pluginSubDirs) {
9766
- const nestedPath = resolve11(dir, parent, subDir);
9767
- if (existsSync18(nestedPath)) {
10048
+ const nestedPath = resolve12(dir, parent, subDir);
10049
+ if (existsSync16(nestedPath)) {
9768
10050
  pushIssue(issues, {
9769
10051
  level: "error",
9770
10052
  code: "plugin-dir-nested",
@@ -9866,11 +10148,11 @@ function lintAgentFrontmatter(agentFiles, issues, frontmatterCache) {
9866
10148
  }
9867
10149
  }
9868
10150
  function collectMarkdownFiles(dir) {
9869
- if (!existsSync18(dir)) return [];
10151
+ if (!existsSync16(dir)) return [];
9870
10152
  const files = [];
9871
10153
  const entries = readdirSync6(dir, { withFileTypes: true });
9872
10154
  for (const entry of entries) {
9873
- const fullPath = resolve11(dir, entry.name);
10155
+ const fullPath = resolve12(dir, entry.name);
9874
10156
  if (entry.isDirectory()) {
9875
10157
  files.push(...collectMarkdownFiles(fullPath));
9876
10158
  } else if (entry.isFile() && entry.name.endsWith(".md")) {
@@ -9897,7 +10179,7 @@ function lintAgentIsolation(agentFiles, issues, frontmatterCache) {
9897
10179
  }
9898
10180
  function lintOpenCodeAgentFrontmatter(dir, config, issues) {
9899
10181
  if (!config.targets.includes("opencode") || !config.agents) return;
9900
- const agents = readCanonicalAgentFiles(resolve11(dir, config.agents));
10182
+ const agents = readCanonicalAgentFiles(resolve12(dir, config.agents));
9901
10183
  for (const agent of agents) {
9902
10184
  if (!("tools" in agent.frontmatter)) continue;
9903
10185
  if (hasCanonicalAgentPermission(agent.frontmatter.permission)) continue;
@@ -9913,6 +10195,77 @@ function lintOpenCodeAgentFrontmatter(dir, config, issues) {
9913
10195
  function hasCanonicalAgentPermission(value) {
9914
10196
  return !!value && typeof value === "object" && !Array.isArray(value);
9915
10197
  }
10198
+ function lintAgentTranslationExplainability(dir, config, issues) {
10199
+ if (!config.agents) return;
10200
+ const agents = readCanonicalAgentFiles(resolve12(dir, config.agents));
10201
+ for (const agent of agents) {
10202
+ const metadata = getCanonicalAgentMetadata(agent);
10203
+ const relativePath = relative7(dir, agent.filePath).replace(/\\/g, "/");
10204
+ if (config.targets.includes("cursor")) {
10205
+ const cursorDegradedFields = [];
10206
+ if (metadata.hidden) cursorDegradedFields.push("hidden");
10207
+ if (metadata.modelReasoningEffort) cursorDegradedFields.push("model_reasoning_effort");
10208
+ if (metadata.sandboxMode) cursorDegradedFields.push("sandbox_mode");
10209
+ if (metadata.permission) cursorDegradedFields.push("permission");
10210
+ if (metadata.tools !== void 0) cursorDegradedFields.push("tools");
10211
+ if (metadata.memory) cursorDegradedFields.push("memory");
10212
+ if (typeof metadata.background === "boolean") cursorDegradedFields.push("background");
10213
+ if (metadata.isolation) cursorDegradedFields.push("isolation");
10214
+ if (cursorDegradedFields.length > 0) {
10215
+ pushIssue(issues, {
10216
+ level: "warning",
10217
+ code: "cursor-agent-translation",
10218
+ message: `Agent fields ${cursorDegradedFields.map((field) => `"${field}"`).join(", ")} are not preserved as first-class Cursor agent frontmatter today. Pluxx translates that intent through subagent framing and generated notes instead.`,
10219
+ file: relativePath,
10220
+ platform: "Cursor"
10221
+ });
10222
+ }
10223
+ }
10224
+ if (config.targets.includes("codex")) {
10225
+ const codexDegradedFields = [];
10226
+ if (metadata.hidden) codexDegradedFields.push("hidden");
10227
+ if (metadata.permission) codexDegradedFields.push("permission");
10228
+ if (metadata.tools !== void 0) codexDegradedFields.push("tools");
10229
+ if (metadata.skills) codexDegradedFields.push("skills");
10230
+ if (metadata.memory) codexDegradedFields.push("memory");
10231
+ if (typeof metadata.background === "boolean") codexDegradedFields.push("background");
10232
+ if (metadata.isolation) codexDegradedFields.push("isolation");
10233
+ if (metadata.color) codexDegradedFields.push("color");
10234
+ if (codexDegradedFields.length > 0) {
10235
+ pushIssue(issues, {
10236
+ level: "warning",
10237
+ code: "codex-agent-translation",
10238
+ message: `Agent fields ${codexDegradedFields.map((field) => `"${field}"`).join(", ")} are not native Codex TOML fields today. Pluxx keeps the specialist behavior, but translates that intent through developer instructions and companion surfaces instead.`,
10239
+ file: relativePath,
10240
+ platform: "Codex"
10241
+ });
10242
+ }
10243
+ }
10244
+ }
10245
+ }
10246
+ function lintCommandTranslationExplainability(dir, config, issues) {
10247
+ if (!config.commands) return;
10248
+ const commands = readCanonicalCommandFiles(resolve12(dir, config.commands));
10249
+ for (const command2 of commands) {
10250
+ const metadata = getCanonicalCommandMetadata(command2);
10251
+ const relativePath = relative7(dir, command2.filePath).replace(/\\/g, "/");
10252
+ if (config.targets.includes("codex")) {
10253
+ const degradedFields = [];
10254
+ if (metadata.agent) degradedFields.push("agent");
10255
+ if (typeof metadata.subtask === "boolean") degradedFields.push("subtask");
10256
+ if (metadata.model) degradedFields.push("model");
10257
+ if (degradedFields.length > 0) {
10258
+ pushIssue(issues, {
10259
+ level: "warning",
10260
+ code: "codex-command-translation",
10261
+ message: `Command fields ${degradedFields.map((field) => `"${field}"`).join(", ")} are not native Codex plugin slash-command fields today. Pluxx keeps the workflow, but degrades those semantics into AGENTS.md routing guidance and \`.codex/commands.generated.json\`.`,
10262
+ file: relativePath,
10263
+ platform: "Codex"
10264
+ });
10265
+ }
10266
+ }
10267
+ }
10268
+ }
9916
10269
  function lintAbsolutePaths(config, issues) {
9917
10270
  const absolutePathPattern = /^\/[a-zA-Z]|^[A-Z]:\\/;
9918
10271
  if (config.hooks) {
@@ -9948,10 +10301,10 @@ function lintAbsolutePaths(config, issues) {
9948
10301
  }
9949
10302
  }
9950
10303
  function lintSettingsJson(dir, issues) {
9951
- const settingsPath = resolve11(dir, "settings.json");
9952
- if (!existsSync18(settingsPath)) return;
10304
+ const settingsPath = resolve12(dir, "settings.json");
10305
+ if (!existsSync16(settingsPath)) return;
9953
10306
  try {
9954
- const content = JSON.parse(readFileSync7(settingsPath, "utf-8"));
10307
+ const content = JSON.parse(readFileSync8(settingsPath, "utf-8"));
9955
10308
  if (typeof content === "object" && content !== null) {
9956
10309
  const keys = Object.keys(content);
9957
10310
  for (const key of keys) {
@@ -10016,11 +10369,11 @@ function lintCodexHooksExternalConfig(config, issues) {
10016
10369
  const codexOverrides = asRecord2(config.platforms?.codex);
10017
10370
  const features = codexOverrides ? asRecord2(codexOverrides.features) : null;
10018
10371
  const hasPluxxCodexHooksFlag = features && features.codex_hooks === true;
10019
- const featureNote = hasPluxxCodexHooksFlag ? "Pluxx will generate `.codex/hooks.generated.json` as a mirror, but you still need to wire the hooks into Codex itself." : "Pluxx will generate `.codex/hooks.generated.json` as a mirror, but you still need to copy or adapt it into `~/.codex/hooks.json` or `<repo>/.codex/hooks.json` and enable `codex_hooks = true` in Codex itself.";
10372
+ if (hasPluxxCodexHooksFlag) return;
10020
10373
  pushIssue(issues, {
10021
10374
  level: "warning",
10022
10375
  code: "codex-hooks-external-config",
10023
- message: `Codex plugin docs currently separate hook configuration from plugin packaging, so Pluxx emits hook guidance as external Codex config rather than as a plugin-bundled hook surface. ${featureNote}`,
10376
+ message: "Pluxx now bundles Codex hooks at `hooks/hooks.json`, but Codex hook loading may still be guarded by `codex_hooks = true` in the host runtime. If bundled hooks do not activate, enable that feature flag and reload Codex.",
10024
10377
  file: "pluxx.config.ts",
10025
10378
  platform: "Codex"
10026
10379
  });
@@ -10084,6 +10437,15 @@ function lintCursorHooks(config, issues) {
10084
10437
  for (const entry of hookEntries) {
10085
10438
  if (!entry || typeof entry !== "object") continue;
10086
10439
  const rec = entry;
10440
+ if (rec.type && rec.type !== "command" && rec.type !== "prompt") {
10441
+ pushIssue(issues, {
10442
+ level: "warning",
10443
+ code: "cursor-hook-type-unsupported",
10444
+ message: `Cursor does not document hook type "${String(rec.type)}". Pluxx currently preserves only command and prompt hooks on the Cursor hook surface.`,
10445
+ file: "pluxx.config.ts",
10446
+ platform: "Cursor"
10447
+ });
10448
+ }
10087
10449
  const cursorLoopLimitEvents = getHookFieldSupportedEvents("cursor", "loop_limit");
10088
10450
  if (rec.loop_limit !== void 0 && !cursorLoopLimitEvents.includes(hookEvent)) {
10089
10451
  pushIssue(issues, {
@@ -10111,19 +10473,46 @@ function lintCursorSkillFrontmatter(config, skillFiles, issues, frontmatterCache
10111
10473
  for (const [key] of parsed.frontmatterFields) {
10112
10474
  for (const [target, supported] of supportedByTarget.entries()) {
10113
10475
  if (supported.has(key)) continue;
10114
- const issue = target === "cursor" ? {
10115
- code: "cursor-skill-frontmatter-unsupported",
10116
- message: `Skill frontmatter field "${key}" is not supported by Cursor. Supported: ${[...supported].join(", ")}`,
10117
- platform: "Cursor"
10118
- } : target === "codex" ? {
10119
- code: "codex-skill-frontmatter-translation",
10120
- message: `Skill frontmatter field "${key}" is not part of documented Codex skill frontmatter. Pluxx may need to translate that intent through AGENTS.md, .codex/agents/*.toml, permissions companions, or runtime config instead of preserving it on SKILL.md.`,
10121
- platform: "Codex"
10122
- } : {
10123
- code: "opencode-skill-frontmatter-translation",
10124
- message: `Skill frontmatter field "${key}" is not part of documented OpenCode skill frontmatter. Pluxx may need to translate that intent through commands, agents, opencode.json, or plugin runtime code instead of preserving it on SKILL.md.`,
10125
- platform: "OpenCode"
10126
- };
10476
+ let issue;
10477
+ if (target === "cursor") {
10478
+ issue = {
10479
+ code: "cursor-skill-frontmatter-unsupported",
10480
+ message: `Skill frontmatter field "${key}" is not supported by Cursor. Supported: ${[...supported].join(", ")}`,
10481
+ platform: "Cursor"
10482
+ };
10483
+ } else if (target === "codex") {
10484
+ const codexFieldTranslation = {
10485
+ "argument-hint": "Pluxx currently translates `argument-hint` into Codex command or routing guidance rather than documented Codex skill frontmatter.",
10486
+ arguments: "Pluxx currently translates skill `arguments` into Codex command or routing guidance rather than documented Codex skill frontmatter.",
10487
+ "allowed-tools": "Pluxx currently translates `allowed-tools` into Codex permission companions or external config rather than documented Codex skill frontmatter.",
10488
+ context: "Pluxx currently treats skill `context` as companion instruction intent because Codex does not document an equivalent skill frontmatter field.",
10489
+ agent: "Pluxx currently translates skill `agent` intent through Codex custom agents or routing guidance rather than documented Codex skill frontmatter.",
10490
+ hooks: "Pluxx currently translates skill-local `hooks` intent through bundled Codex hooks, where skill-local attachment is lost.",
10491
+ paths: "Pluxx currently translates skill `paths` into surrounding instruction or routing context because Codex does not document an equivalent skill frontmatter field.",
10492
+ shell: "Pluxx currently translates skill `shell` intent through command or runtime guidance because Codex does not document an equivalent skill frontmatter field."
10493
+ };
10494
+ issue = {
10495
+ code: "codex-skill-frontmatter-translation",
10496
+ message: codexFieldTranslation[key] ?? `Skill frontmatter field "${key}" is not part of documented Codex skill frontmatter. Pluxx may need to translate that intent through AGENTS.md, .codex/agents/*.toml, permissions companions, or runtime config instead of preserving it on SKILL.md.`,
10497
+ platform: "Codex"
10498
+ };
10499
+ } else {
10500
+ const opencodeFieldTranslation = {
10501
+ "argument-hint": "Pluxx currently translates `argument-hint` into OpenCode commands or runtime command metadata rather than documented OpenCode skill frontmatter.",
10502
+ arguments: "Pluxx currently translates skill `arguments` into OpenCode commands or runtime command metadata rather than documented OpenCode skill frontmatter.",
10503
+ "allowed-tools": "Pluxx currently translates `allowed-tools` into OpenCode permission config rather than documented OpenCode skill frontmatter.",
10504
+ context: "Pluxx currently translates skill `context` through runtime instruction injection or neighboring config because OpenCode does not document an equivalent skill frontmatter field.",
10505
+ agent: "Pluxx currently translates skill `agent` intent through OpenCode agents or runtime routing rather than documented OpenCode skill frontmatter.",
10506
+ hooks: "Pluxx currently translates skill-local `hooks` intent through OpenCode runtime event handlers, where skill-local attachment is lost.",
10507
+ paths: "Pluxx currently translates skill `paths` into runtime or instruction context because OpenCode does not document an equivalent skill frontmatter field.",
10508
+ shell: "Pluxx currently translates skill `shell` intent through runtime code or command execution guidance because OpenCode does not document an equivalent skill frontmatter field."
10509
+ };
10510
+ issue = {
10511
+ code: "opencode-skill-frontmatter-translation",
10512
+ message: opencodeFieldTranslation[key] ?? `Skill frontmatter field "${key}" is not part of documented OpenCode skill frontmatter. Pluxx may need to translate that intent through commands, agents, opencode.json, or plugin runtime code instead of preserving it on SKILL.md.`,
10513
+ platform: "OpenCode"
10514
+ };
10515
+ }
10127
10516
  pushIssue(issues, {
10128
10517
  level: "warning",
10129
10518
  file: skillFile,
@@ -10307,6 +10696,20 @@ function lintPermissions(config, issues) {
10307
10696
  platform: "Codex"
10308
10697
  });
10309
10698
  }
10699
+ if (config.targets.includes("cursor")) {
10700
+ const cursorNuance = [];
10701
+ if (rules.some((rule) => rule.kind === "Skill")) cursorNuance.push("Skill(...)");
10702
+ if (rules.some((rule) => rule.kind === "MCP")) cursorNuance.push("MCP(...)");
10703
+ if (cursorNuance.length > 0) {
10704
+ pushIssue(issues, {
10705
+ level: "warning",
10706
+ code: "cursor-permissions-translation",
10707
+ message: `Cursor permission intent is split across generated hooks, CLI permission config, and subagent/tool framing. ${cursorNuance.join(" and ")} selectors are especially important to verify because they do not map to one first-class native Cursor permission field.`,
10708
+ file: "pluxx.config.ts",
10709
+ platform: "Cursor"
10710
+ });
10711
+ }
10712
+ }
10310
10713
  if (rules.some((rule) => rule.kind === "Skill")) {
10311
10714
  const limitedTargets = config.targets.filter((target) => !["claude-code", "codex", "opencode"].includes(target));
10312
10715
  if (limitedTargets.length > 0) {
@@ -10393,11 +10796,13 @@ async function lintProject(dir = process.cwd(), options = {}) {
10393
10796
  lintSettingsJson(dir, issues);
10394
10797
  lintLegacyCommandsDir(dir, lintConfig, issues);
10395
10798
  lintHookEvents(lintConfig, issues);
10396
- const agentsDir = resolve11(dir, lintConfig.agents ?? "agents");
10397
- const agentFiles = existsSync18(agentsDir) ? collectMarkdownFiles(agentsDir) : [];
10799
+ const agentsDir = resolve12(dir, lintConfig.agents ?? "agents");
10800
+ const agentFiles = existsSync16(agentsDir) ? collectMarkdownFiles(agentsDir) : [];
10398
10801
  lintAgentFrontmatter(agentFiles, issues, frontmatterCache);
10399
10802
  lintAgentIsolation(agentFiles, issues, frontmatterCache);
10400
10803
  lintOpenCodeAgentFrontmatter(dir, { ...lintConfig, agents: lintConfig.agents ?? "./agents/" }, issues);
10804
+ lintAgentTranslationExplainability(dir, { ...lintConfig, agents: lintConfig.agents ?? "./agents/" }, issues);
10805
+ lintCommandTranslationExplainability(dir, lintConfig, issues);
10401
10806
  lintMcpUrls(lintConfig, issues);
10402
10807
  lintMcpRuntimeState(dir, lintConfig, issues);
10403
10808
  lintInstallerOwnedRuntimeScripts(lintConfig, issues);
@@ -10414,9 +10819,9 @@ async function lintProject(dir = process.cwd(), options = {}) {
10414
10819
  lintCodexCommandGuidance(lintConfig, issues);
10415
10820
  lintCursorHooks(lintConfig, issues);
10416
10821
  lintCursorRuleContentLimits(lintConfig, issues);
10417
- const skillsDir = resolve11(dir, lintConfig.skills);
10822
+ const skillsDir = resolve12(dir, lintConfig.skills);
10418
10823
  let skillFiles = [];
10419
- if (!existsSync18(skillsDir)) {
10824
+ if (!existsSync16(skillsDir)) {
10420
10825
  pushIssue(issues, {
10421
10826
  level: "error",
10422
10827
  code: "skills-dir-missing",
@@ -10460,7 +10865,7 @@ function printLintResult(result, dir = process.cwd()) {
10460
10865
  for (const issue of visibleIssues) {
10461
10866
  const levelLabel = issue.level === "error" ? "ERROR" : "WARN ";
10462
10867
  const platformLabel = issue.platform ? `[${issue.platform}] ` : "";
10463
- const loc = issue.file ? `${relative7(dir, resolve11(dir, issue.file))}: ` : "";
10868
+ const loc = issue.file ? `${relative7(dir, resolve12(dir, issue.file))}: ` : "";
10464
10869
  console.log(`${levelLabel} ${issue.code} ${platformLabel}${loc}${issue.message}`);
10465
10870
  }
10466
10871
  const primitiveLines = renderPrimitiveTranslationSummary(result.primitiveSummary);
@@ -10486,17 +10891,17 @@ async function runLint(dir = process.cwd()) {
10486
10891
  }
10487
10892
 
10488
10893
  // src/cli/test.ts
10489
- import { existsSync as existsSync21 } from "fs";
10490
- import { resolve as resolve14 } from "path";
10894
+ import { existsSync as existsSync19 } from "fs";
10895
+ import { resolve as resolve15 } from "path";
10491
10896
 
10492
10897
  // src/cli/eval.ts
10493
- import { existsSync as existsSync20, readFileSync as readFileSync8 } from "fs";
10494
- import { resolve as resolve13 } from "path";
10898
+ import { existsSync as existsSync18, readFileSync as readFileSync9 } from "fs";
10899
+ import { resolve as resolve14 } from "path";
10495
10900
 
10496
10901
  // src/cli/init-from-mcp.ts
10497
- import { existsSync as existsSync19, lstatSync as lstatSync2 } from "fs";
10902
+ import { existsSync as existsSync17, lstatSync as lstatSync2 } from "fs";
10498
10903
  import { mkdir as mkdir2 } from "fs/promises";
10499
- import { basename as basename5, dirname as dirname4, relative as relative8, resolve as resolve12 } from "path";
10904
+ import { basename as basename5, dirname as dirname4, relative as relative8, resolve as resolve13 } from "path";
10500
10905
 
10501
10906
  // src/user-config.ts
10502
10907
  var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
@@ -10804,10 +11209,10 @@ async function writeMcpScaffold(options) {
10804
11209
  }
10805
11210
  async function applyMcpScaffoldPlan(rootDir, plan) {
10806
11211
  for (const file of plan.files) {
10807
- const filePath = resolve12(rootDir, file.relativePath);
11212
+ const filePath = resolve13(rootDir, file.relativePath);
10808
11213
  const parentDir = file.relativePath.split("/").slice(0, -1).join("/");
10809
11214
  if (parentDir) {
10810
- await mkdir2(resolve12(rootDir, parentDir), { recursive: true });
11215
+ await mkdir2(resolve13(rootDir, parentDir), { recursive: true });
10811
11216
  }
10812
11217
  await writeTextFile(filePath, file.content);
10813
11218
  }
@@ -10835,9 +11240,9 @@ async function planMcpScaffold(options) {
10835
11240
  const permissions = options.permissions ?? (options.approveMcpTools ? { allow: [`MCP(${serverName}.*)`] } : void 0);
10836
11241
  const passthroughPaths = inferLocalRuntimePassthroughPaths(options.rootDir, options.source);
10837
11242
  const runtimeAuthMode = options.runtimeAuthMode ?? (options.source.transport !== "stdio" && options.source.auth?.type === "platform" ? "platform" : "inline");
10838
- const instructionsPath = resolve12(options.rootDir, "INSTRUCTIONS.md");
10839
- const skillRoot = resolve12(options.rootDir, "skills");
10840
- const commandsRoot = resolve12(options.rootDir, "commands");
11243
+ const instructionsPath = resolve13(options.rootDir, "INSTRUCTIONS.md");
11244
+ const skillRoot = resolve13(options.rootDir, "skills");
11245
+ const commandsRoot = resolve13(options.rootDir, "commands");
10841
11246
  const userConfigSource = {
10842
11247
  targets: options.targets,
10843
11248
  mcp: {
@@ -10857,7 +11262,7 @@ async function planMcpScaffold(options) {
10857
11262
  const taxonomyPath = MCP_TAXONOMY_PATH;
10858
11263
  const files = [];
10859
11264
  const addPlannedFile = async (relativePath, content) => {
10860
- const filePath = resolve12(options.rootDir, relativePath);
11265
+ const filePath = resolve13(options.rootDir, relativePath);
10861
11266
  const action = await planTextFileAction(filePath, content);
10862
11267
  files.push({ relativePath, content, action });
10863
11268
  };
@@ -10907,7 +11312,7 @@ async function planMcpScaffold(options) {
10907
11312
  );
10908
11313
  for (const skill of plannedSkills) {
10909
11314
  const relativeSkillPath = `skills/${skill.dirName}`;
10910
- const skillPath = resolve12(skillRoot, skill.dirName, "SKILL.md");
11315
+ const skillPath = resolve13(skillRoot, skill.dirName, "SKILL.md");
10911
11316
  await addPlannedFile(
10912
11317
  `${relativeSkillPath}/SKILL.md`,
10913
11318
  wrapManagedMarkdown(
@@ -10924,7 +11329,7 @@ async function planMcpScaffold(options) {
10924
11329
  }
10925
11330
  for (const skill of plannedCommands) {
10926
11331
  const relativeCommandPath = `commands/${skill.dirName}.md`;
10927
- const commandPath = resolve12(commandsRoot, `${skill.dirName}.md`);
11332
+ const commandPath = resolve13(commandsRoot, `${skill.dirName}.md`);
10928
11333
  await addPlannedFile(
10929
11334
  relativeCommandPath,
10930
11335
  buildCommandContent(
@@ -11348,8 +11753,8 @@ function inferLocalRuntimePassthroughPaths(rootDir, source) {
11348
11753
  const candidates = [source.command, ...source.args ?? []];
11349
11754
  for (const candidate of candidates) {
11350
11755
  if (!isLikelyLocalRuntimePath2(candidate)) continue;
11351
- const resolvedPath = resolve12(rootDir, candidate);
11352
- if (!existsSync19(resolvedPath)) continue;
11756
+ const resolvedPath = resolve13(rootDir, candidate);
11757
+ if (!existsSync17(resolvedPath)) continue;
11353
11758
  const stats = lstatSync2(resolvedPath);
11354
11759
  const runtimeDir = stats.isDirectory() ? resolvedPath : dirname4(resolvedPath);
11355
11760
  const normalized = normalizePassthroughDir(rootDir, runtimeDir);
@@ -11401,6 +11806,16 @@ function serializeHooks(hooks) {
11401
11806
  entry.command ? `command: ${JSON.stringify(entry.command)}` : null,
11402
11807
  entry.prompt ? `prompt: ${JSON.stringify(entry.prompt)}` : null,
11403
11808
  entry.model ? `model: ${JSON.stringify(entry.model)}` : null,
11809
+ entry.url ? `url: ${JSON.stringify(entry.url)}` : null,
11810
+ entry.headers ? `headers: ${JSON.stringify(entry.headers)}` : null,
11811
+ entry.allowedEnvVars ? `allowedEnvVars: ${JSON.stringify(entry.allowedEnvVars)}` : null,
11812
+ entry.server ? `server: ${JSON.stringify(entry.server)}` : null,
11813
+ entry.tool ? `tool: ${JSON.stringify(entry.tool)}` : null,
11814
+ entry.input ? `input: ${JSON.stringify(entry.input)}` : null,
11815
+ entry.if ? `if: ${JSON.stringify(entry.if)}` : null,
11816
+ entry.async !== void 0 ? `async: ${entry.async}` : null,
11817
+ entry.asyncRewake !== void 0 ? `asyncRewake: ${entry.asyncRewake}` : null,
11818
+ entry.shell ? `shell: ${JSON.stringify(entry.shell)}` : null,
11404
11819
  entry.timeout !== void 0 ? `timeout: ${entry.timeout}` : null,
11405
11820
  entry.matcher ? `matcher: ${JSON.stringify(entry.matcher)}` : null,
11406
11821
  entry.failClosed !== void 0 ? `failClosed: ${entry.failClosed}` : null,
@@ -12403,11 +12818,11 @@ function summarizeChecks(checks) {
12403
12818
  };
12404
12819
  }
12405
12820
  async function loadMcpScaffoldMetadata(rootDir) {
12406
- const metadataPath = resolve13(rootDir, MCP_SCAFFOLD_METADATA_PATH);
12407
- if (!existsSync20(metadataPath)) {
12821
+ const metadataPath = resolve14(rootDir, MCP_SCAFFOLD_METADATA_PATH);
12822
+ if (!existsSync18(metadataPath)) {
12408
12823
  return null;
12409
12824
  }
12410
- const parsed = JSON.parse(readFileSync8(metadataPath, "utf-8"));
12825
+ const parsed = JSON.parse(readFileSync9(metadataPath, "utf-8"));
12411
12826
  return parsed;
12412
12827
  }
12413
12828
  function hasRelatedDiscovery(metadata) {
@@ -12434,8 +12849,8 @@ function collectSkillPromptLabels(skill) {
12434
12849
  }
12435
12850
  function evaluateInstructions(rootDir, metadata, checks) {
12436
12851
  const relativePath = "INSTRUCTIONS.md";
12437
- const filePath = resolve13(rootDir, relativePath);
12438
- if (!existsSync20(filePath)) {
12852
+ const filePath = resolve14(rootDir, relativePath);
12853
+ if (!existsSync18(filePath)) {
12439
12854
  addCheck(checks, {
12440
12855
  level: "error",
12441
12856
  code: "instructions-missing",
@@ -12446,7 +12861,7 @@ function evaluateInstructions(rootDir, metadata, checks) {
12446
12861
  });
12447
12862
  return;
12448
12863
  }
12449
- const content = readFileSync8(filePath, "utf-8");
12864
+ const content = readFileSync9(filePath, "utf-8");
12450
12865
  const missing = [];
12451
12866
  if (!content.includes("## Workflow Guidance")) missing.push("`## Workflow Guidance`");
12452
12867
  if (!content.includes("## Tool Routing")) missing.push("`## Tool Routing`");
@@ -12480,12 +12895,12 @@ function evaluateSkills(rootDir, metadata, checks) {
12480
12895
  const failures = [];
12481
12896
  for (const skill of metadata.skills) {
12482
12897
  const relativePath = `skills/${skill.dirName}/SKILL.md`;
12483
- const filePath = resolve13(rootDir, relativePath);
12484
- if (!existsSync20(filePath)) {
12898
+ const filePath = resolve14(rootDir, relativePath);
12899
+ if (!existsSync18(filePath)) {
12485
12900
  failures.push({ path: relativePath, missing: ["skill file"] });
12486
12901
  continue;
12487
12902
  }
12488
- const content = readFileSync8(filePath, "utf-8");
12903
+ const content = readFileSync9(filePath, "utf-8");
12489
12904
  const missing = [];
12490
12905
  if (!content.includes("## Example Requests")) {
12491
12906
  missing.push("`## Example Requests`");
@@ -12544,12 +12959,12 @@ function evaluateCommands(rootDir, metadata, checks) {
12544
12959
  const commandSkillNames = managedCommandSkillNames(metadata);
12545
12960
  for (const skill of metadata.skills.filter((entry) => commandSkillNames.has(entry.dirName))) {
12546
12961
  const relativePath = `commands/${skill.dirName}.md`;
12547
- const filePath = resolve13(rootDir, relativePath);
12548
- if (!existsSync20(filePath)) {
12962
+ const filePath = resolve14(rootDir, relativePath);
12963
+ if (!existsSync18(filePath)) {
12549
12964
  failures.push({ path: relativePath, missing: ["command file"] });
12550
12965
  continue;
12551
12966
  }
12552
- const content = readFileSync8(filePath, "utf-8");
12967
+ const content = readFileSync9(filePath, "utf-8");
12553
12968
  const missing = [];
12554
12969
  if (!content.includes("argument-hint:")) missing.push("`argument-hint` frontmatter");
12555
12970
  if (!content.includes("Primary tools:")) missing.push("`Primary tools:` block");
@@ -12824,7 +13239,7 @@ async function runTestSuite(options = {}) {
12824
13239
  const evalReport = await runEvalSuite({ rootDir });
12825
13240
  const checks = targets.map((platform) => {
12826
13241
  const requiredPath = SMOKE_PATHS[platform];
12827
- const ok = existsSync21(resolve14(rootDir, config.outDir, platform, requiredPath));
13242
+ const ok = existsSync19(resolve15(rootDir, config.outDir, platform, requiredPath));
12828
13243
  return { platform, requiredPath, ok };
12829
13244
  });
12830
13245
  return {
@@ -12881,8 +13296,8 @@ function printTestResult(result) {
12881
13296
  }
12882
13297
 
12883
13298
  // src/cli/sync-from-mcp.ts
12884
- import { cpSync as cpSync2, existsSync as existsSync22, mkdtempSync, readFileSync as readFileSync9, rmSync as rmSync2, readdirSync as readdirSync7, rmdirSync, writeFileSync as writeFileSync3 } from "fs";
12885
- import { dirname as dirname5, isAbsolute, relative as relative9, resolve as resolve15 } from "path";
13299
+ import { cpSync as cpSync2, existsSync as existsSync20, mkdtempSync, readFileSync as readFileSync10, rmSync as rmSync2, readdirSync as readdirSync7, rmdirSync, writeFileSync as writeFileSync3 } from "fs";
13300
+ import { dirname as dirname5, isAbsolute, relative as relative9, resolve as resolve16 } from "path";
12886
13301
  import { tmpdir } from "os";
12887
13302
 
12888
13303
  // src/mcp/introspect.ts
@@ -13164,10 +13579,10 @@ async function createSseClient(server) {
13164
13579
  let resolveEndpoint;
13165
13580
  let rejectEndpoint;
13166
13581
  let endpointSettled = false;
13167
- const endpointReady = new Promise((resolve27, reject) => {
13582
+ const endpointReady = new Promise((resolve28, reject) => {
13168
13583
  resolveEndpoint = (value) => {
13169
13584
  endpointSettled = true;
13170
- resolve27(value);
13585
+ resolve28(value);
13171
13586
  };
13172
13587
  rejectEndpoint = (error) => {
13173
13588
  endpointSettled = true;
@@ -13304,7 +13719,7 @@ async function createSseClient(server) {
13304
13719
  async request(method, params) {
13305
13720
  const requestId = nextRequestId();
13306
13721
  const endpoint = endpointUrl ?? await endpointReady;
13307
- const resultPromise = new Promise((resolve27, reject) => {
13722
+ const resultPromise = new Promise((resolve28, reject) => {
13308
13723
  const timeout = setTimeout(() => {
13309
13724
  pending.delete(requestId);
13310
13725
  reject(new McpIntrospectionError(`Timed out waiting for MCP SSE response to ${method}.`));
@@ -13312,7 +13727,7 @@ async function createSseClient(server) {
13312
13727
  pending.set(requestId, {
13313
13728
  resolve: (value) => {
13314
13729
  clearTimeout(timeout);
13315
- resolve27(value);
13730
+ resolve28(value);
13316
13731
  },
13317
13732
  reject: (error) => {
13318
13733
  clearTimeout(timeout);
@@ -13465,7 +13880,7 @@ async function createStdioClient(server) {
13465
13880
  method,
13466
13881
  ...params ? { params } : {}
13467
13882
  });
13468
- return new Promise((resolve27, reject) => {
13883
+ return new Promise((resolve28, reject) => {
13469
13884
  const timeout = setTimeout(() => {
13470
13885
  pending.delete(id);
13471
13886
  reject(new McpIntrospectionError(`Timed out waiting for MCP stdio response to ${method}.`));
@@ -13473,7 +13888,7 @@ async function createStdioClient(server) {
13473
13888
  pending.set(id, {
13474
13889
  resolve: (value) => {
13475
13890
  clearTimeout(timeout);
13476
- resolve27(value);
13891
+ resolve28(value);
13477
13892
  },
13478
13893
  reject: (error) => {
13479
13894
  clearTimeout(timeout);
@@ -13651,12 +14066,12 @@ function nextRequestId() {
13651
14066
  // src/cli/sync-from-mcp.ts
13652
14067
  async function readMcpScaffoldMetadata(rootDir) {
13653
14068
  const filepath = resolveWithinRoot(rootDir, MCP_SCAFFOLD_METADATA_PATH);
13654
- if (!existsSync22(filepath)) {
14069
+ if (!existsSync20(filepath)) {
13655
14070
  throw new Error(
13656
14071
  `No MCP scaffold metadata found at ${MCP_SCAFFOLD_METADATA_PATH}. Run "pluxx init --from-mcp" first.`
13657
14072
  );
13658
14073
  }
13659
- return JSON.parse(readFileSync9(filepath, "utf-8"));
14074
+ return JSON.parse(readFileSync10(filepath, "utf-8"));
13660
14075
  }
13661
14076
  async function syncFromMcp(options) {
13662
14077
  const metadata = await readMcpScaffoldMetadata(options.rootDir);
@@ -13682,19 +14097,19 @@ async function syncFromMcp(options) {
13682
14097
  toolRenames
13683
14098
  });
13684
14099
  const newMetadataPath = resolveWithinRoot(options.rootDir, MCP_SCAFFOLD_METADATA_PATH);
13685
- const newMetadata = JSON.parse(readFileSync9(newMetadataPath, "utf-8"));
14100
+ const newMetadata = JSON.parse(readFileSync10(newMetadataPath, "utf-8"));
13686
14101
  const skillRenames = detectSkillRenames(metadata.skills, newMetadata.skills, toolRenames);
13687
14102
  for (const [oldSkillDir, newSkillDir] of skillRenames) {
13688
14103
  const oldSkillPath = resolveWithinRoot(options.rootDir, `skills/${oldSkillDir}/SKILL.md`);
13689
- if (!existsSync22(oldSkillPath)) continue;
13690
- const oldContent = readFileSync9(oldSkillPath, "utf-8");
14104
+ if (!existsSync20(oldSkillPath)) continue;
14105
+ const oldContent = readFileSync10(oldSkillPath, "utf-8");
13691
14106
  const extracted = extractMixedMarkdownContent(oldContent, "");
13692
14107
  if (!hasMeaningfulCustomContent(oldContent)) continue;
13693
14108
  const newSkill = newMetadata.skills.find((s) => s.dirName === newSkillDir);
13694
14109
  if (!newSkill) continue;
13695
14110
  const newSkillPath = resolveWithinRoot(options.rootDir, `skills/${newSkill.dirName}/SKILL.md`);
13696
- if (!existsSync22(newSkillPath)) continue;
13697
- const currentContent = readFileSync9(newSkillPath, "utf-8");
14111
+ if (!existsSync20(newSkillPath)) continue;
14112
+ const currentContent = readFileSync10(newSkillPath, "utf-8");
13698
14113
  const updatedContent = injectCustomContent(currentContent, extracted.customContent);
13699
14114
  writeFileSync3(newSkillPath, updatedContent, "utf-8");
13700
14115
  }
@@ -13736,8 +14151,8 @@ async function syncFromMcp(options) {
13736
14151
  if (!beforeManaged.has(file)) return false;
13737
14152
  const before = beforeContents.get(file);
13738
14153
  const currentPath = resolveWithinRoot(options.rootDir, file);
13739
- if (!existsSync22(currentPath)) return false;
13740
- const after = readFileSync9(currentPath, "utf-8");
14154
+ if (!existsSync20(currentPath)) return false;
14155
+ const after = readFileSync10(currentPath, "utf-8");
13741
14156
  return before !== after;
13742
14157
  });
13743
14158
  const scaffoldChanged = addedFiles.length > 0 || updatedFiles.length > 0 || removedFiles.length > 0 || renamedFiles.length > 0;
@@ -13783,7 +14198,7 @@ async function applyPersistedTaxonomy(rootDir) {
13783
14198
  writeFileSync3(resolveWithinRoot(rootDir, instructionsPath), previousInstructions, "utf-8");
13784
14199
  }
13785
14200
  const newMetadataPath = resolveWithinRoot(rootDir, MCP_SCAFFOLD_METADATA_PATH);
13786
- const newMetadata = JSON.parse(readFileSync9(newMetadataPath, "utf-8"));
14201
+ const newMetadata = JSON.parse(readFileSync10(newMetadataPath, "utf-8"));
13787
14202
  const skillRenames = detectSkillRenames(metadata.skills, newMetadata.skills, /* @__PURE__ */ new Map());
13788
14203
  preserveCustomContentForRenames(rootDir, skillRenames, (dirName) => `skills/${dirName}/SKILL.md`);
13789
14204
  preserveCustomContentForRenames(rootDir, skillRenames, (dirName) => `commands/${dirName}.md`);
@@ -13811,8 +14226,8 @@ async function applyPersistedTaxonomy(rootDir) {
13811
14226
  invalidateSavedAgentPack(rootDir);
13812
14227
  }
13813
14228
  async function planSyncFromMcp(options) {
13814
- const tempRoot = mkdtempSync(resolve15(tmpdir(), "pluxx-sync-dry-run-"));
13815
- const projectDir = resolve15(tempRoot, "project");
14229
+ const tempRoot = mkdtempSync(resolve16(tmpdir(), "pluxx-sync-dry-run-"));
14230
+ const projectDir = resolve16(tempRoot, "project");
13816
14231
  try {
13817
14232
  cpSync2(options.rootDir, projectDir, { recursive: true });
13818
14233
  return await syncFromMcp({
@@ -13825,8 +14240,8 @@ async function planSyncFromMcp(options) {
13825
14240
  }
13826
14241
  function readPersistedSkills(rootDir, metadata) {
13827
14242
  const taxonomyPath = resolveWithinRoot(rootDir, MCP_TAXONOMY_PATH);
13828
- if (existsSync22(taxonomyPath)) {
13829
- return JSON.parse(readFileSync9(taxonomyPath, "utf-8"));
14243
+ if (existsSync20(taxonomyPath)) {
14244
+ return JSON.parse(readFileSync10(taxonomyPath, "utf-8"));
13830
14245
  }
13831
14246
  return metadata.skills.map((skill) => ({
13832
14247
  dirName: skill.dirName,
@@ -13838,13 +14253,13 @@ function readPersistedSkills(rootDir, metadata) {
13838
14253
  function preserveCustomContentForRenames(rootDir, renames, pathForName) {
13839
14254
  for (const [oldName, newName] of renames) {
13840
14255
  const oldPath = resolveWithinRoot(rootDir, pathForName(oldName));
13841
- if (!existsSync22(oldPath)) continue;
13842
- const oldContent = readFileSync9(oldPath, "utf-8");
14256
+ if (!existsSync20(oldPath)) continue;
14257
+ const oldContent = readFileSync10(oldPath, "utf-8");
13843
14258
  const extracted = extractMixedMarkdownContent(oldContent, "");
13844
14259
  if (!hasMeaningfulCustomContent(oldContent)) continue;
13845
14260
  const newPath = resolveWithinRoot(rootDir, pathForName(newName));
13846
- if (!existsSync22(newPath)) continue;
13847
- const currentContent = readFileSync9(newPath, "utf-8");
14261
+ if (!existsSync20(newPath)) continue;
14262
+ const currentContent = readFileSync10(newPath, "utf-8");
13848
14263
  const updatedContent = injectCustomContent(currentContent, extracted.customContent);
13849
14264
  writeFileSync3(newPath, updatedContent, "utf-8");
13850
14265
  }
@@ -13853,26 +14268,26 @@ function snapshotManagedFiles(rootDir, files) {
13853
14268
  const contents = /* @__PURE__ */ new Map();
13854
14269
  for (const file of files) {
13855
14270
  const filepath = resolveWithinRoot(rootDir, file);
13856
- if (!existsSync22(filepath)) continue;
13857
- contents.set(file, readFileSync9(filepath, "utf-8"));
14271
+ if (!existsSync20(filepath)) continue;
14272
+ contents.set(file, readFileSync10(filepath, "utf-8"));
13858
14273
  }
13859
14274
  return contents;
13860
14275
  }
13861
14276
  function removeManagedFile(rootDir, relativePath) {
13862
14277
  const filepath = resolveWithinRoot(rootDir, relativePath);
13863
- if (!existsSync22(filepath)) return;
14278
+ if (!existsSync20(filepath)) return;
13864
14279
  rmSync2(filepath, { force: true });
13865
14280
  pruneEmptyDirectories(rootDir, dirname5(filepath));
13866
14281
  }
13867
14282
  function shouldPreserveManagedFile(rootDir, relativePath) {
13868
14283
  if (!relativePath.endsWith(".md")) return false;
13869
14284
  const filepath = resolveWithinRoot(rootDir, relativePath);
13870
- if (!existsSync22(filepath)) return false;
13871
- return hasMeaningfulCustomContent(readFileSync9(filepath, "utf-8"));
14285
+ if (!existsSync20(filepath)) return false;
14286
+ return hasMeaningfulCustomContent(readFileSync10(filepath, "utf-8"));
13872
14287
  }
13873
14288
  function pruneEmptyDirectories(rootDir, startDir) {
13874
14289
  let current = startDir;
13875
- const stopDir = resolve15(rootDir);
14290
+ const stopDir = resolve16(rootDir);
13876
14291
  while (current.startsWith(stopDir) && current !== stopDir) {
13877
14292
  const entries = readdirSync7(current);
13878
14293
  if (entries.length > 0) return;
@@ -14039,8 +14454,8 @@ function computeSkillRenameScore(oldSkill, newSkill, toolRenames) {
14039
14454
  return score;
14040
14455
  }
14041
14456
  function resolveWithinRoot(rootDir, relativePath) {
14042
- const rootPath = resolve15(rootDir);
14043
- const filepath = resolve15(rootPath, relativePath);
14457
+ const rootPath = resolve16(rootDir);
14458
+ const filepath = resolve16(rootPath, relativePath);
14044
14459
  const relativePathFromRoot = relative9(rootPath, filepath);
14045
14460
  if (relativePathFromRoot === "" || !relativePathFromRoot.startsWith("..") && !isAbsolute(relativePathFromRoot)) {
14046
14461
  return filepath;
@@ -14057,13 +14472,13 @@ function formatSyncSummary(result, rootDir) {
14057
14472
  result.renamedFiles.forEach((rename) => lines.push(` \u2192 ${rename.from} \u2192 ${rename.to}`));
14058
14473
  }
14059
14474
  lines.push(`Added: ${result.addedFiles.length}`);
14060
- result.addedFiles.forEach((file) => lines.push(` + ${relative9(rootDir, resolve15(rootDir, file))}`));
14475
+ result.addedFiles.forEach((file) => lines.push(` + ${relative9(rootDir, resolve16(rootDir, file))}`));
14061
14476
  lines.push(`Updated: ${result.updatedFiles.length}`);
14062
- result.updatedFiles.forEach((file) => lines.push(` ~ ${relative9(rootDir, resolve15(rootDir, file))}`));
14477
+ result.updatedFiles.forEach((file) => lines.push(` ~ ${relative9(rootDir, resolve16(rootDir, file))}`));
14063
14478
  lines.push(`Removed: ${result.removedFiles.length}`);
14064
- result.removedFiles.forEach((file) => lines.push(` - ${relative9(rootDir, resolve15(rootDir, file))}`));
14479
+ result.removedFiles.forEach((file) => lines.push(` - ${relative9(rootDir, resolve16(rootDir, file))}`));
14065
14480
  lines.push(`Preserved: ${result.preservedFiles.length}`);
14066
- result.preservedFiles.forEach((file) => lines.push(` ! ${relative9(rootDir, resolve15(rootDir, file))}`));
14481
+ result.preservedFiles.forEach((file) => lines.push(` ! ${relative9(rootDir, resolve16(rootDir, file))}`));
14067
14482
  return lines;
14068
14483
  }
14069
14484
 
@@ -14139,10 +14554,10 @@ async function planAgentPrepare(rootDir = process.cwd(), options = {}) {
14139
14554
  }
14140
14555
  async function applyAgentPreparePlan(rootDir, plan) {
14141
14556
  for (const file of plan.files) {
14142
- const filePath = resolve16(rootDir, file.relativePath);
14557
+ const filePath = resolve17(rootDir, file.relativePath);
14143
14558
  const parentDir = file.relativePath.split("/").slice(0, -1).join("/");
14144
14559
  if (parentDir) {
14145
- await mkdir3(resolve16(rootDir, parentDir), { recursive: true });
14560
+ await mkdir3(resolve17(rootDir, parentDir), { recursive: true });
14146
14561
  }
14147
14562
  await writeTextFile(filePath, file.content);
14148
14563
  }
@@ -14151,8 +14566,8 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
14151
14566
  const config = await loadConfig(rootDir);
14152
14567
  const project = await loadAgentProjectModel(rootDir, config);
14153
14568
  const overrides = await loadAgentOverrides(rootDir);
14154
- const contextPath = resolve16(rootDir, AGENT_CONTEXT_PATH);
14155
- if (!options.allowMissingContext && !existsSync23(contextPath)) {
14569
+ const contextPath = resolve17(rootDir, AGENT_CONTEXT_PATH);
14570
+ if (!options.allowMissingContext && !existsSync21(contextPath)) {
14156
14571
  throw new Error(`No agent context found at ${AGENT_CONTEXT_PATH}. Run "pluxx agent prepare" first.`);
14157
14572
  }
14158
14573
  if (project.sourceKind !== "mcp-derived" && kind !== "review") {
@@ -14164,7 +14579,7 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
14164
14579
  displayName: project.displayName,
14165
14580
  skillPaths: project.skills.map((skill) => skill.path),
14166
14581
  commandPaths: project.commands.map((command2) => command2.path),
14167
- extraContextPaths: [AGENT_SOURCES_PATH, AGENT_DOCS_CONTEXT_PATH].filter((path) => existsSync23(resolve16(rootDir, path))),
14582
+ extraContextPaths: [AGENT_SOURCES_PATH, AGENT_DOCS_CONTEXT_PATH].filter((path) => existsSync21(resolve17(rootDir, path))),
14168
14583
  sourceKind: project.sourceKind,
14169
14584
  taxonomyPath: project.taxonomyPath,
14170
14585
  overrides
@@ -14181,10 +14596,10 @@ async function planAgentPrompt(rootDir, kind, options = {}) {
14181
14596
  }
14182
14597
  async function applyAgentPromptPlan(rootDir, plan) {
14183
14598
  for (const file of plan.files) {
14184
- const filePath = resolve16(rootDir, file.relativePath);
14599
+ const filePath = resolve17(rootDir, file.relativePath);
14185
14600
  const parentDir = file.relativePath.split("/").slice(0, -1).join("/");
14186
14601
  if (parentDir) {
14187
- await mkdir3(resolve16(rootDir, parentDir), { recursive: true });
14602
+ await mkdir3(resolve17(rootDir, parentDir), { recursive: true });
14188
14603
  }
14189
14604
  await writeTextFile(filePath, file.content);
14190
14605
  }
@@ -14281,10 +14696,10 @@ async function refreshAgentPack(rootDir, prepareOptions) {
14281
14696
  }
14282
14697
  async function writePlannedFiles(rootDir, files) {
14283
14698
  for (const file of files) {
14284
- const filePath = resolve16(rootDir, file.relativePath);
14699
+ const filePath = resolve17(rootDir, file.relativePath);
14285
14700
  const parentDir = file.relativePath.split("/").slice(0, -1).join("/");
14286
14701
  if (parentDir) {
14287
- await mkdir3(resolve16(rootDir, parentDir), { recursive: true });
14702
+ await mkdir3(resolve17(rootDir, parentDir), { recursive: true });
14288
14703
  }
14289
14704
  await writeTextFile(filePath, file.content);
14290
14705
  }
@@ -14331,8 +14746,8 @@ function buildProtectedFiles() {
14331
14746
  ];
14332
14747
  }
14333
14748
  async function loadMcpScaffoldMetadata2(rootDir) {
14334
- const metadataPath = resolve16(rootDir, MCP_SCAFFOLD_METADATA_PATH);
14335
- if (!existsSync23(metadataPath)) {
14749
+ const metadataPath = resolve17(rootDir, MCP_SCAFFOLD_METADATA_PATH);
14750
+ if (!existsSync21(metadataPath)) {
14336
14751
  throw new Error(`No MCP scaffold metadata found at ${MCP_SCAFFOLD_METADATA_PATH}. Run "pluxx init --from-mcp" first.`);
14337
14752
  }
14338
14753
  try {
@@ -14345,8 +14760,8 @@ async function loadMcpScaffoldMetadata2(rootDir) {
14345
14760
  }
14346
14761
  }
14347
14762
  async function loadAgentProjectModel(rootDir, config) {
14348
- const metadataPath = resolve16(rootDir, MCP_SCAFFOLD_METADATA_PATH);
14349
- if (existsSync23(metadataPath)) {
14763
+ const metadataPath = resolve17(rootDir, MCP_SCAFFOLD_METADATA_PATH);
14764
+ if (existsSync21(metadataPath)) {
14350
14765
  const metadata = await loadMcpScaffoldMetadata2(rootDir);
14351
14766
  const serverEntry = Object.entries(config.mcp ?? {})[0];
14352
14767
  const [serverName, server] = serverEntry ?? ["unknown", metadata.source];
@@ -14380,8 +14795,8 @@ async function loadAgentProjectModel(rootDir, config) {
14380
14795
  }
14381
14796
  function loadManualAgentProjectModel(rootDir, config) {
14382
14797
  const displayName = config.brand?.displayName ?? config.name;
14383
- const skillsDir = config.skills ? resolve16(rootDir, config.skills) : void 0;
14384
- const commandsDir = config.commands ? resolve16(rootDir, config.commands) : void 0;
14798
+ const skillsDir = config.skills ? resolve17(rootDir, config.skills) : void 0;
14799
+ const commandsDir = config.commands ? resolve17(rootDir, config.commands) : void 0;
14385
14800
  const skills = readCanonicalSkillFiles2(rootDir, skillsDir);
14386
14801
  const commands = readCanonicalCommandFiles(commandsDir).map((command2) => ({
14387
14802
  path: normalizeRelativePath(relative10(rootDir, command2.filePath)),
@@ -14407,7 +14822,7 @@ function loadManualAgentProjectModel(rootDir, config) {
14407
14822
  };
14408
14823
  }
14409
14824
  async function planFile(rootDir, relativePath, content) {
14410
- const filePath = resolve16(rootDir, relativePath);
14825
+ const filePath = resolve17(rootDir, relativePath);
14411
14826
  const action = await planTextFileAction(filePath, content);
14412
14827
  return { relativePath, content, action };
14413
14828
  }
@@ -14715,8 +15130,8 @@ async function collectAgentContextPackInternal(rootDir, options, overrides) {
14715
15130
  for (const relativePath of contextPaths) {
14716
15131
  if (seenFilePaths.has(relativePath)) continue;
14717
15132
  seenFilePaths.add(relativePath);
14718
- const filePath = resolve16(rootDir, relativePath);
14719
- if (!existsSync23(filePath)) {
15133
+ const filePath = resolve17(rootDir, relativePath);
15134
+ if (!existsSync21(filePath)) {
14720
15135
  const source2 = {
14721
15136
  label: relativePath,
14722
15137
  kind: "file",
@@ -16004,12 +16419,12 @@ async function resolveAgentRunnerModel(runner, explicitModel) {
16004
16419
  };
16005
16420
  }
16006
16421
  async function readCodexDefaultModel() {
16007
- const codexHome = process.env.CODEX_HOME?.trim() || resolve16(homedir(), ".codex");
16008
- return await readTomlStringValue(resolve16(codexHome, "config.toml"), "model");
16422
+ const codexHome = process.env.CODEX_HOME?.trim() || resolve17(homedir(), ".codex");
16423
+ return await readTomlStringValue(resolve17(codexHome, "config.toml"), "model");
16009
16424
  }
16010
16425
  async function readOpenCodeDefaultModel() {
16011
- const configHome = process.env.XDG_CONFIG_HOME?.trim() || resolve16(homedir(), ".config");
16012
- const configPath = resolve16(configHome, "opencode", "opencode.json");
16426
+ const configHome = process.env.XDG_CONFIG_HOME?.trim() || resolve17(homedir(), ".config");
16427
+ const configPath = resolve17(configHome, "opencode", "opencode.json");
16013
16428
  const parsed = await readJsonFile(configPath);
16014
16429
  if (!parsed || typeof parsed !== "object") {
16015
16430
  return void 0;
@@ -16027,9 +16442,9 @@ async function readOpenCodeDefaultModel() {
16027
16442
  }
16028
16443
  async function readClaudeDefaultModel() {
16029
16444
  for (const candidate of [
16030
- resolve16(homedir(), ".claude", "settings.json"),
16031
- resolve16(homedir(), ".claude", "settings.local.json"),
16032
- resolve16(homedir(), ".claude.json")
16445
+ resolve17(homedir(), ".claude", "settings.json"),
16446
+ resolve17(homedir(), ".claude", "settings.local.json"),
16447
+ resolve17(homedir(), ".claude.json")
16033
16448
  ]) {
16034
16449
  const parsed = await readJsonFile(candidate);
16035
16450
  if (!parsed || typeof parsed !== "object") continue;
@@ -16113,8 +16528,8 @@ async function executeCommand(command2, cwd, options = {}) {
16113
16528
  let codexLastMessagePath = null;
16114
16529
  const isClaudeStreamJson = runtimeCommand[0] === "claude" && runtimeCommand.includes("--output-format") && runtimeCommand.includes("stream-json");
16115
16530
  if (runtimeCommand[0] === "codex" && runtimeCommand[1] === "exec") {
16116
- codexOutputDir = await mkdtemp(resolve16(tmpdir2(), "pluxx-codex-output-"));
16117
- codexLastMessagePath = resolve16(codexOutputDir, "last-message.txt");
16531
+ codexOutputDir = await mkdtemp(resolve17(tmpdir2(), "pluxx-codex-output-"));
16532
+ codexLastMessagePath = resolve17(codexOutputDir, "last-message.txt");
16118
16533
  runtimeCommand.splice(2, 0, "--json", "--output-last-message", codexLastMessagePath);
16119
16534
  }
16120
16535
  return await new Promise((resolvePromise, reject) => {
@@ -16134,7 +16549,7 @@ async function executeCommand(command2, cwd, options = {}) {
16134
16549
  let claudeTurnCompleted = false;
16135
16550
  let claudeTurnFailed = false;
16136
16551
  const sentinelInterval = codexLastMessagePath || isClaudeStreamJson ? setInterval(() => {
16137
- const sawCompletionSignal = codexTurnCompleted || codexTurnFailed || claudeTurnCompleted || claudeTurnFailed || (codexLastMessagePath ? existsSync23(codexLastMessagePath) : false);
16552
+ const sawCompletionSignal = codexTurnCompleted || codexTurnFailed || claudeTurnCompleted || claudeTurnFailed || (codexLastMessagePath ? existsSync21(codexLastMessagePath) : false);
16138
16553
  if (!sawCompletionSignal) return;
16139
16554
  if (sawFinalMessageAt == null) {
16140
16555
  sawFinalMessageAt = Date.now();
@@ -16233,8 +16648,8 @@ async function prepareRunnerExecution(runner) {
16233
16648
  if (!cursorBinary || cursorBinary === AGENT_RUNNER_BINARIES.cursor) {
16234
16649
  return { env: process.env };
16235
16650
  }
16236
- const shimDir = await mkdtemp(resolve16(tmpdir2(), "pluxx-cursor-bin-"));
16237
- const shimPath = resolve16(shimDir, AGENT_RUNNER_BINARIES.cursor);
16651
+ const shimDir = await mkdtemp(resolve17(tmpdir2(), "pluxx-cursor-bin-"));
16652
+ const shimPath = resolve17(shimDir, AGENT_RUNNER_BINARIES.cursor);
16238
16653
  await writeTextFile(
16239
16654
  shimPath,
16240
16655
  `#!/bin/sh
@@ -16255,21 +16670,21 @@ exec ${shellQuote(cursorBinary)} "$@"
16255
16670
  if (runner !== "codex") {
16256
16671
  return { env: process.env };
16257
16672
  }
16258
- const currentCodexHome = process.env.CODEX_HOME?.trim() || resolve16(homedir(), ".codex");
16259
- const isolatedCodexHome = await mkdtemp(resolve16(tmpdir2(), "pluxx-codex-home-"));
16260
- await mkdir3(resolve16(isolatedCodexHome, "memories"), { recursive: true });
16673
+ const currentCodexHome = process.env.CODEX_HOME?.trim() || resolve17(homedir(), ".codex");
16674
+ const isolatedCodexHome = await mkdtemp(resolve17(tmpdir2(), "pluxx-codex-home-"));
16675
+ await mkdir3(resolve17(isolatedCodexHome, "memories"), { recursive: true });
16261
16676
  for (const relativePath of ["auth.json", "config.toml", "hooks.json", "installation_id"]) {
16262
- const sourcePath = resolve16(currentCodexHome, relativePath);
16263
- if (!existsSync23(sourcePath)) continue;
16264
- await copyFile(sourcePath, resolve16(isolatedCodexHome, relativePath));
16677
+ const sourcePath = resolve17(currentCodexHome, relativePath);
16678
+ if (!existsSync21(sourcePath)) continue;
16679
+ await copyFile(sourcePath, resolve17(isolatedCodexHome, relativePath));
16265
16680
  }
16266
- const rulesSourceDir = resolve16(currentCodexHome, "rules");
16267
- if (existsSync23(rulesSourceDir)) {
16268
- const rulesTargetDir = resolve16(isolatedCodexHome, "rules");
16681
+ const rulesSourceDir = resolve17(currentCodexHome, "rules");
16682
+ if (existsSync21(rulesSourceDir)) {
16683
+ const rulesTargetDir = resolve17(isolatedCodexHome, "rules");
16269
16684
  await mkdir3(rulesTargetDir, { recursive: true });
16270
- const defaultRulesPath = resolve16(rulesSourceDir, "default.rules");
16271
- if (existsSync23(defaultRulesPath)) {
16272
- await copyFile(defaultRulesPath, resolve16(rulesTargetDir, "default.rules"));
16685
+ const defaultRulesPath = resolve17(rulesSourceDir, "default.rules");
16686
+ if (existsSync21(defaultRulesPath)) {
16687
+ await copyFile(defaultRulesPath, resolve17(rulesTargetDir, "default.rules"));
16273
16688
  }
16274
16689
  }
16275
16690
  return {
@@ -16305,8 +16720,8 @@ function titleCase(value) {
16305
16720
  return value.charAt(0).toUpperCase() + value.slice(1);
16306
16721
  }
16307
16722
  async function loadAgentOverrides(rootDir) {
16308
- const overridesPath = resolve16(rootDir, AGENT_OVERRIDES_PATH);
16309
- if (!existsSync23(overridesPath)) {
16723
+ const overridesPath = resolve17(rootDir, AGENT_OVERRIDES_PATH);
16724
+ if (!existsSync21(overridesPath)) {
16310
16725
  return null;
16311
16726
  }
16312
16727
  const content = await readTextFile(overridesPath);
@@ -16409,14 +16824,45 @@ ${additions.map((block) => `- ${block.replace(/\n/g, "\n ")}`).join("\n")}
16409
16824
 
16410
16825
  // src/cli/doctor.ts
16411
16826
  import { spawn as spawn3 } from "child_process";
16412
- import { accessSync, constants, existsSync as existsSync25, lstatSync as lstatSync3, readFileSync as readFileSync12, readdirSync as readdirSync10 } from "fs";
16413
- import { basename as basename6, dirname as dirname7, resolve as resolve18 } from "path";
16827
+ import { accessSync, constants, existsSync as existsSync23, lstatSync as lstatSync3, readFileSync as readFileSync13, readdirSync as readdirSync10 } from "fs";
16828
+ import { basename as basename6, dirname as dirname7, resolve as resolve19 } from "path";
16414
16829
 
16415
16830
  // src/cli/install.ts
16416
- import { resolve as resolve17, dirname as dirname6 } from "path";
16417
- import { existsSync as existsSync24, symlinkSync, mkdirSync as mkdirSync4, rmSync as rmSync3, readFileSync as readFileSync11, writeFileSync as writeFileSync4, cpSync as cpSync3, readdirSync as readdirSync9 } from "fs";
16831
+ import { resolve as resolve18, dirname as dirname6 } from "path";
16832
+ import { existsSync as existsSync22, symlinkSync, mkdirSync as mkdirSync4, rmSync as rmSync3, readFileSync as readFileSync12, writeFileSync as writeFileSync4, cpSync as cpSync3, readdirSync as readdirSync9 } from "fs";
16418
16833
  import { spawnSync } from "child_process";
16419
16834
  import * as readline2 from "readline";
16835
+
16836
+ // src/distribution-lifecycle.ts
16837
+ var INSTALL_FOLLOWUP_NOTES = {
16838
+ "claude-code": "Claude Code note: if Claude is already open, run /reload-plugins in the session to pick up the new install.",
16839
+ cursor: "Cursor note: if Cursor is already open, use Developer: Reload Window or restart Cursor to pick up the new install.",
16840
+ codex: "Codex note: if Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex to pick up the new install. Plugin-bundled MCP servers may appear on the plugin detail page without appearing in the global MCP servers settings page.",
16841
+ opencode: "OpenCode note: if OpenCode is already open, restart or reload it so the plugin is picked up."
16842
+ };
16843
+ var VERIFY_STALE_ACTIONS = {
16844
+ codex: "in Codex, use Plugins > Refresh if available, or restart Codex so the plugin cache reloads"
16845
+ };
16846
+ var PUBLISH_RELOAD_INSTRUCTIONS = {
16847
+ "claude-code": "If Claude is already open, run /reload-plugins in the active session.",
16848
+ cursor: "If Cursor is already open, use Developer: Reload Window or restart Cursor so the plugin is picked up.",
16849
+ codex: "If Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex so the plugin is picked up.",
16850
+ opencode: "If OpenCode is already open, restart or reload it so the plugin is picked up."
16851
+ };
16852
+ function getInstallFollowupNote(target) {
16853
+ return INSTALL_FOLLOWUP_NOTES[target];
16854
+ }
16855
+ function getInstallFollowupNotes(targets) {
16856
+ return targets.map((target) => getInstallFollowupNote(target)).filter((note) => Boolean(note));
16857
+ }
16858
+ function getVerifyInstallStaleAction(target) {
16859
+ return VERIFY_STALE_ACTIONS[target];
16860
+ }
16861
+ function getPublishReloadInstruction(target) {
16862
+ return PUBLISH_RELOAD_INSTRUCTIONS[target];
16863
+ }
16864
+
16865
+ // src/cli/install.ts
16420
16866
  function listHookCommands(hooks) {
16421
16867
  if (!hooks) return [];
16422
16868
  const commands = [];
@@ -16568,57 +17014,57 @@ function getInstallTargets(pluginName) {
16568
17014
  return [
16569
17015
  {
16570
17016
  platform: "claude-code",
16571
- pluginDir: resolve17(home, ".claude/plugins", pluginName),
17017
+ pluginDir: resolve18(home, ".claude/plugins", pluginName),
16572
17018
  description: `claude plugin install ${pluginName}@${getClaudeMarketplaceName(pluginName)}`
16573
17019
  },
16574
17020
  {
16575
17021
  platform: "cursor",
16576
- pluginDir: resolve17(home, ".cursor/plugins/local", pluginName),
17022
+ pluginDir: resolve18(home, ".cursor/plugins/local", pluginName),
16577
17023
  description: `~/.cursor/plugins/local/${pluginName}`
16578
17024
  },
16579
17025
  {
16580
17026
  platform: "codex",
16581
- pluginDir: resolve17(home, ".codex/plugins", pluginName),
17027
+ pluginDir: resolve18(home, ".codex/plugins", pluginName),
16582
17028
  description: `~/.codex/plugins/${pluginName} (via ~/.agents/plugins/marketplace.json)`
16583
17029
  },
16584
17030
  {
16585
17031
  platform: "opencode",
16586
- pluginDir: resolve17(home, ".config/opencode/plugins", pluginName),
17032
+ pluginDir: resolve18(home, ".config/opencode/plugins", pluginName),
16587
17033
  description: `~/.config/opencode/plugins/${pluginName}.ts + ~/.config/opencode/plugins/${pluginName}/`
16588
17034
  },
16589
17035
  {
16590
17036
  platform: "github-copilot",
16591
- pluginDir: resolve17(home, ".github-copilot/plugins", pluginName),
17037
+ pluginDir: resolve18(home, ".github-copilot/plugins", pluginName),
16592
17038
  description: `~/.github-copilot/plugins/${pluginName}`
16593
17039
  },
16594
17040
  {
16595
17041
  platform: "openhands",
16596
- pluginDir: resolve17(home, ".openhands/plugins", pluginName),
17042
+ pluginDir: resolve18(home, ".openhands/plugins", pluginName),
16597
17043
  description: `~/.openhands/plugins/${pluginName}`
16598
17044
  },
16599
17045
  {
16600
17046
  platform: "warp",
16601
- pluginDir: resolve17(home, ".warp/plugins", pluginName),
17047
+ pluginDir: resolve18(home, ".warp/plugins", pluginName),
16602
17048
  description: `~/.warp/plugins/${pluginName}`
16603
17049
  },
16604
17050
  {
16605
17051
  platform: "gemini-cli",
16606
- pluginDir: resolve17(home, ".gemini/extensions", pluginName),
17052
+ pluginDir: resolve18(home, ".gemini/extensions", pluginName),
16607
17053
  description: `~/.gemini/extensions/${pluginName}`
16608
17054
  },
16609
17055
  {
16610
17056
  platform: "roo-code",
16611
- pluginDir: resolve17(home, ".roo/plugins", pluginName),
17057
+ pluginDir: resolve18(home, ".roo/plugins", pluginName),
16612
17058
  description: `~/.roo/plugins/${pluginName}`
16613
17059
  },
16614
17060
  {
16615
17061
  platform: "cline",
16616
- pluginDir: resolve17(home, ".cline/plugins", pluginName),
17062
+ pluginDir: resolve18(home, ".cline/plugins", pluginName),
16617
17063
  description: `~/.cline/plugins/${pluginName}`
16618
17064
  },
16619
17065
  {
16620
17066
  platform: "amp",
16621
- pluginDir: resolve17(home, ".amp/plugins", pluginName),
17067
+ pluginDir: resolve18(home, ".amp/plugins", pluginName),
16622
17068
  description: `~/.amp/plugins/${pluginName}`
16623
17069
  }
16624
17070
  ];
@@ -16659,10 +17105,10 @@ function writeOpenCodeEntryFile(pluginDir, pluginName) {
16659
17105
  }
16660
17106
  function getOpenCodeSkillRoot() {
16661
17107
  const home = process.env.HOME ?? "~";
16662
- return resolve17(home, ".config/opencode/skills");
17108
+ return resolve18(home, ".config/opencode/skills");
16663
17109
  }
16664
17110
  function getOpenCodeInstalledSkillDir(pluginName, skillName) {
16665
- return resolve17(getOpenCodeSkillRoot(), `${pluginName}-${skillName}`);
17111
+ return resolve18(getOpenCodeSkillRoot(), `${pluginName}-${skillName}`);
16666
17112
  }
16667
17113
  function namespaceOpenCodeSkill(content, pluginName, fallbackName) {
16668
17114
  const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---\n?/);
@@ -16686,29 +17132,29 @@ ${nextFrontmatter}
16686
17132
  ${content.slice(frontmatterMatch[0].length)}`;
16687
17133
  }
16688
17134
  function syncOpenCodeSkills(pluginDir, pluginName) {
16689
- const sourceSkillsDir = resolve17(pluginDir, "skills");
16690
- if (!existsSync24(sourceSkillsDir)) return;
17135
+ const sourceSkillsDir = resolve18(pluginDir, "skills");
17136
+ if (!existsSync22(sourceSkillsDir)) return;
16691
17137
  mkdirSync4(getOpenCodeSkillRoot(), { recursive: true });
16692
17138
  for (const entry of readdirSync9(sourceSkillsDir, { withFileTypes: true })) {
16693
17139
  if (!entry.isDirectory()) continue;
16694
- const skillSourceDir = resolve17(sourceSkillsDir, entry.name);
16695
- if (!existsSync24(resolve17(skillSourceDir, "SKILL.md"))) continue;
17140
+ const skillSourceDir = resolve18(sourceSkillsDir, entry.name);
17141
+ if (!existsSync22(resolve18(skillSourceDir, "SKILL.md"))) continue;
16696
17142
  const installedSkillDir = getOpenCodeInstalledSkillDir(pluginName, entry.name);
16697
17143
  rmSync3(installedSkillDir, { recursive: true, force: true });
16698
17144
  cpSync3(skillSourceDir, installedSkillDir, { recursive: true });
16699
- const skillPath = resolve17(installedSkillDir, "SKILL.md");
17145
+ const skillPath = resolve18(installedSkillDir, "SKILL.md");
16700
17146
  writeFileSync4(
16701
17147
  skillPath,
16702
- namespaceOpenCodeSkill(readFileSync11(skillPath, "utf-8"), pluginName, entry.name)
17148
+ namespaceOpenCodeSkill(readFileSync12(skillPath, "utf-8"), pluginName, entry.name)
16703
17149
  );
16704
17150
  }
16705
17151
  }
16706
17152
  function verifyOpenCodeInstall(pluginDir, pluginName) {
16707
17153
  const entryPath = getOpenCodeEntryPath(pluginDir);
16708
- if (!existsSync24(entryPath)) {
17154
+ if (!existsSync22(entryPath)) {
16709
17155
  throw new Error(`OpenCode install is incomplete: missing host entry file at ${entryPath}`);
16710
17156
  }
16711
- const entryContent = readFileSync11(entryPath, "utf-8");
17157
+ const entryContent = readFileSync12(entryPath, "utf-8");
16712
17158
  const expectedImport = `import * as PluginModule from "./${pluginName}/index.ts"`;
16713
17159
  if (!entryContent.includes(expectedImport)) {
16714
17160
  throw new Error(`OpenCode install is incomplete: ${entryPath} does not import ./${pluginName}/index.ts`);
@@ -16717,17 +17163,17 @@ function verifyOpenCodeInstall(pluginDir, pluginName) {
16717
17163
  if (!entryContent.includes(expectedDirectoryBridge)) {
16718
17164
  throw new Error(`OpenCode install is incomplete: ${entryPath} does not preserve the plugin root bridge`);
16719
17165
  }
16720
- const sourceSkillsDir = resolve17(pluginDir, "skills");
16721
- if (!existsSync24(sourceSkillsDir)) return;
17166
+ const sourceSkillsDir = resolve18(pluginDir, "skills");
17167
+ if (!existsSync22(sourceSkillsDir)) return;
16722
17168
  for (const entry of readdirSync9(sourceSkillsDir, { withFileTypes: true })) {
16723
17169
  if (!entry.isDirectory()) continue;
16724
- const sourceSkillPath = resolve17(sourceSkillsDir, entry.name, "SKILL.md");
16725
- if (!existsSync24(sourceSkillPath)) continue;
16726
- const installedSkillPath = resolve17(getOpenCodeInstalledSkillDir(pluginName, entry.name), "SKILL.md");
16727
- if (!existsSync24(installedSkillPath)) {
17170
+ const sourceSkillPath = resolve18(sourceSkillsDir, entry.name, "SKILL.md");
17171
+ if (!existsSync22(sourceSkillPath)) continue;
17172
+ const installedSkillPath = resolve18(getOpenCodeInstalledSkillDir(pluginName, entry.name), "SKILL.md");
17173
+ if (!existsSync22(installedSkillPath)) {
16728
17174
  throw new Error(`OpenCode install is incomplete: missing synced skill at ${installedSkillPath}`);
16729
17175
  }
16730
- const installedSkillContent = readFileSync11(installedSkillPath, "utf-8");
17176
+ const installedSkillContent = readFileSync12(installedSkillPath, "utf-8");
16731
17177
  if (!installedSkillContent.includes(`${pluginName}/`)) {
16732
17178
  throw new Error(`OpenCode install is incomplete: ${installedSkillPath} is missing the expected ${pluginName}/ skill namespace`);
16733
17179
  }
@@ -16735,30 +17181,17 @@ function verifyOpenCodeInstall(pluginDir, pluginName) {
16735
17181
  }
16736
17182
  function removeOpenCodeSkills(pluginName) {
16737
17183
  const root = getOpenCodeSkillRoot();
16738
- if (!existsSync24(root)) return false;
17184
+ if (!existsSync22(root)) return false;
16739
17185
  let removed = false;
16740
17186
  for (const entry of readdirSync9(root, { withFileTypes: true })) {
16741
17187
  if (!entry.name.startsWith(`${pluginName}-`)) continue;
16742
- rmSync3(resolve17(root, entry.name), { recursive: true, force: true });
17188
+ rmSync3(resolve18(root, entry.name), { recursive: true, force: true });
16743
17189
  removed = true;
16744
17190
  }
16745
17191
  return removed;
16746
17192
  }
16747
- function getInstallFollowupNotes(platforms) {
16748
- const notes = [];
16749
- if (platforms.includes("claude-code")) {
16750
- notes.push("Claude Code note: if Claude is already open, run /reload-plugins in the session to pick up the new install.");
16751
- }
16752
- if (platforms.includes("cursor")) {
16753
- notes.push("Cursor note: if Cursor is already open, use Developer: Reload Window or restart Cursor to pick up the new install.");
16754
- }
16755
- if (platforms.includes("codex")) {
16756
- notes.push("Codex note: if Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex to pick up the new install. Plugin-bundled MCP servers may appear on the plugin detail page without appearing in the global MCP servers settings page.");
16757
- }
16758
- if (platforms.includes("opencode")) {
16759
- notes.push("OpenCode note: if OpenCode is already open, restart or reload it so the plugin is picked up.");
16760
- }
16761
- return notes;
17193
+ function getInstallFollowupNotes2(platforms) {
17194
+ return getInstallFollowupNotes(platforms);
16762
17195
  }
16763
17196
  function runCommandDefault(command2, args2) {
16764
17197
  const result = spawnSync(command2, args2, { encoding: "utf-8" });
@@ -16769,9 +17202,9 @@ function runCommandDefault(command2, args2) {
16769
17202
  };
16770
17203
  }
16771
17204
  function createSymlinkInstall(target) {
16772
- const parentDir = resolve17(target.pluginDir, "..");
17205
+ const parentDir = resolve18(target.pluginDir, "..");
16773
17206
  mkdirSync4(parentDir, { recursive: true });
16774
- if (existsSync24(target.pluginDir)) {
17207
+ if (existsSync22(target.pluginDir)) {
16775
17208
  rmSync3(target.pluginDir, { recursive: true, force: true });
16776
17209
  }
16777
17210
  symlinkSync(target.sourceDir, target.pluginDir);
@@ -16783,13 +17216,13 @@ function createOpenCodeSymlinkInstall(target, pluginName) {
16783
17216
  }
16784
17217
  function getCodexMarketplacePath() {
16785
17218
  const home = process.env.HOME ?? "~";
16786
- return resolve17(home, ".agents/plugins/marketplace.json");
17219
+ return resolve18(home, ".agents/plugins/marketplace.json");
16787
17220
  }
16788
17221
  function getCodexMarketplacePluginPath(pluginName) {
16789
17222
  return `./.codex/plugins/${pluginName}`;
16790
17223
  }
16791
17224
  function readCodexMarketplace(filepath) {
16792
- if (!existsSync24(filepath)) {
17225
+ if (!existsSync22(filepath)) {
16793
17226
  return {
16794
17227
  name: "pluxx-local",
16795
17228
  interface: {
@@ -16798,7 +17231,7 @@ function readCodexMarketplace(filepath) {
16798
17231
  plugins: []
16799
17232
  };
16800
17233
  }
16801
- const raw = readFileSync11(filepath, "utf-8");
17234
+ const raw = readFileSync12(filepath, "utf-8");
16802
17235
  const parsed = JSON.parse(raw);
16803
17236
  return {
16804
17237
  name: parsed.name ?? "pluxx-local",
@@ -16834,7 +17267,7 @@ function ensureCodexMarketplace(pluginName) {
16834
17267
  }
16835
17268
  function removeCodexMarketplacePlugin(pluginName) {
16836
17269
  const filepath = getCodexMarketplacePath();
16837
- if (!existsSync24(filepath)) return;
17270
+ if (!existsSync22(filepath)) return;
16838
17271
  const marketplace = readCodexMarketplace(filepath);
16839
17272
  const nextPlugins = (marketplace.plugins ?? []).filter((plugin) => plugin.name !== pluginName);
16840
17273
  if (nextPlugins.length === (marketplace.plugins ?? []).length) {
@@ -16854,9 +17287,9 @@ function removeCodexMarketplacePlugin(pluginName) {
16854
17287
  );
16855
17288
  }
16856
17289
  function createCopiedInstall(target) {
16857
- const parentDir = resolve17(target.pluginDir, "..");
17290
+ const parentDir = resolve18(target.pluginDir, "..");
16858
17291
  mkdirSync4(parentDir, { recursive: true });
16859
- if (existsSync24(target.pluginDir)) {
17292
+ if (existsSync22(target.pluginDir)) {
16860
17293
  rmSync3(target.pluginDir, { recursive: true, force: true });
16861
17294
  }
16862
17295
  cpSync3(target.sourceDir, target.pluginDir, { recursive: true });
@@ -16880,8 +17313,8 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
16880
17313
  if (!config.mcp) return;
16881
17314
  const env = buildUserConfigEnvMap(entries);
16882
17315
  if (platform === "claude-code" || platform === "cursor") {
16883
- const filepath = resolve17(pluginDir, platform === "claude-code" ? ".mcp.json" : "mcp.json");
16884
- if (!existsSync24(filepath)) return;
17316
+ const filepath = resolve18(pluginDir, platform === "claude-code" ? ".mcp.json" : "mcp.json");
17317
+ if (!existsSync22(filepath)) return;
16885
17318
  const mcpServers = {};
16886
17319
  const usesPlatformManagedAuth = platform === "claude-code" ? config.platforms?.["claude-code"]?.mcpAuth === "platform" : config.platforms?.cursor?.mcpAuth === "platform";
16887
17320
  for (const [name, server] of Object.entries(config.mcp)) {
@@ -16912,8 +17345,8 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
16912
17345
  return;
16913
17346
  }
16914
17347
  if (platform === "codex") {
16915
- const filepath = resolve17(pluginDir, ".mcp.json");
16916
- if (!existsSync24(filepath)) return;
17348
+ const filepath = resolve18(pluginDir, ".mcp.json");
17349
+ if (!existsSync22(filepath)) return;
16917
17350
  const mcpServers = {};
16918
17351
  for (const [name, server] of Object.entries(config.mcp)) {
16919
17352
  if (server.transport === "stdio") {
@@ -16943,7 +17376,7 @@ function patchInstalledMcpConfig(pluginDir, platform, config, entries) {
16943
17376
  }
16944
17377
  function writeInstalledUserConfig(pluginDir, entries) {
16945
17378
  if (entries.length === 0) return;
16946
- const filepath = resolve17(pluginDir, ".pluxx-user.json");
17379
+ const filepath = resolve18(pluginDir, ".pluxx-user.json");
16947
17380
  const payload = {
16948
17381
  values: buildUserConfigValueMap(entries),
16949
17382
  env: buildUserConfigEnvMap(entries)
@@ -16952,8 +17385,8 @@ function writeInstalledUserConfig(pluginDir, entries) {
16952
17385
  }
16953
17386
  function disableInstalledEnvValidation(pluginDir, entries) {
16954
17387
  if (entries.length === 0) return;
16955
- const filepath = resolve17(pluginDir, "scripts/check-env.sh");
16956
- if (!existsSync24(filepath)) return;
17388
+ const filepath = resolve18(pluginDir, "scripts/check-env.sh");
17389
+ if (!existsSync22(filepath)) return;
16957
17390
  writeFileSync4(
16958
17391
  filepath,
16959
17392
  "#!/usr/bin/env bash\nset -euo pipefail\n# pluxx install materialized required config for this local plugin install.\nexit 0\n"
@@ -16981,15 +17414,15 @@ function getClaudeMarketplaceName(pluginName) {
16981
17414
  }
16982
17415
  function getClaudeMarketplaceRoot(pluginName) {
16983
17416
  const home = process.env.HOME ?? "~";
16984
- return resolve17(home, ".claude/plugins/data", getClaudeMarketplaceName(pluginName));
17417
+ return resolve18(home, ".claude/plugins/data", getClaudeMarketplaceName(pluginName));
16985
17418
  }
16986
17419
  function readBundleManifestVersion(rootDir, platform) {
16987
17420
  const manifestPath = manifestPathForPlatform(platform);
16988
17421
  if (!manifestPath) return void 0;
16989
- const filepath = resolve17(rootDir, manifestPath);
16990
- if (!existsSync24(filepath)) return void 0;
17422
+ const filepath = resolve18(rootDir, manifestPath);
17423
+ if (!existsSync22(filepath)) return void 0;
16991
17424
  try {
16992
- const manifest = JSON.parse(readFileSync11(filepath, "utf-8"));
17425
+ const manifest = JSON.parse(readFileSync12(filepath, "utf-8"));
16993
17426
  return typeof manifest.version === "string" ? manifest.version : void 0;
16994
17427
  } catch {
16995
17428
  return void 0;
@@ -16998,7 +17431,7 @@ function readBundleManifestVersion(rootDir, platform) {
16998
17431
  function resolveClaudeInstalledCachePath(pluginName, version) {
16999
17432
  if (!version) return void 0;
17000
17433
  const home = process.env.HOME ?? "~";
17001
- return resolve17(home, ".claude/plugins/cache", getClaudeMarketplaceName(pluginName), pluginName, version);
17434
+ return resolve18(home, ".claude/plugins/cache", getClaudeMarketplaceName(pluginName), pluginName, version);
17002
17435
  }
17003
17436
  function resolveExpectedInstalledConsumerPath(target, pluginName) {
17004
17437
  if (target.platform === "claude-code" && pluginName !== "") {
@@ -17013,8 +17446,8 @@ function resolveExpectedInstalledConsumerPath(target, pluginName) {
17013
17446
  function resolveInstalledConsumerPath(target, pluginName) {
17014
17447
  if (target.platform === "claude-code" && pluginName !== "") {
17015
17448
  const expectedPath = resolveExpectedInstalledConsumerPath(target, pluginName);
17016
- if (existsSync24(expectedPath)) return expectedPath;
17017
- if (existsSync24(target.pluginDir)) return target.pluginDir;
17449
+ if (existsSync22(expectedPath)) return expectedPath;
17450
+ if (existsSync22(target.pluginDir)) return target.pluginDir;
17018
17451
  return expectedPath;
17019
17452
  }
17020
17453
  return target.pluginDir;
@@ -17038,11 +17471,11 @@ function isRelativeBundlePath(value) {
17038
17471
  }
17039
17472
  function resolveBundleReference(rootDir, value) {
17040
17473
  if (isRelativeBundlePath(value)) {
17041
- return resolve17(rootDir, value);
17474
+ return resolve18(rootDir, value);
17042
17475
  }
17043
17476
  const pluginRootMatch = value.match(/^\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/](.+)$/);
17044
17477
  if (pluginRootMatch) {
17045
- return resolve17(rootDir, pluginRootMatch[1]);
17478
+ return resolve18(rootDir, pluginRootMatch[1]);
17046
17479
  }
17047
17480
  return void 0;
17048
17481
  }
@@ -17095,8 +17528,8 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
17095
17528
  invalidRuntimeScripts: []
17096
17529
  };
17097
17530
  }
17098
- const manifestFile = resolve17(rootDir, manifestPath);
17099
- if (!existsSync24(manifestFile)) {
17531
+ const manifestFile = resolve18(rootDir, manifestPath);
17532
+ if (!existsSync22(manifestFile)) {
17100
17533
  return {
17101
17534
  manifestIssue: `missing plugin manifest at ${manifestPath}`,
17102
17535
  missingManifestPaths: [],
@@ -17106,7 +17539,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
17106
17539
  }
17107
17540
  let manifest;
17108
17541
  try {
17109
- manifest = JSON.parse(readFileSync11(manifestFile, "utf-8"));
17542
+ manifest = JSON.parse(readFileSync12(manifestFile, "utf-8"));
17110
17543
  } catch (error) {
17111
17544
  return {
17112
17545
  manifestIssue: `plugin manifest at ${manifestPath} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
@@ -17117,7 +17550,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
17117
17550
  }
17118
17551
  const missingManifestPaths = readBundleManifestReferences(manifest).filter((value) => {
17119
17552
  const resolved = resolveBundleReference(rootDir, value);
17120
- return resolved !== void 0 && !existsSync24(resolved);
17553
+ return resolved !== void 0 && !existsSync22(resolved);
17121
17554
  }).sort();
17122
17555
  const hooksReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
17123
17556
  if (!hooksReference) {
@@ -17128,7 +17561,7 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
17128
17561
  };
17129
17562
  }
17130
17563
  const hooksPath = resolveBundleReference(rootDir, hooksReference);
17131
- if (!hooksPath || !existsSync24(hooksPath)) {
17564
+ if (!hooksPath || !existsSync22(hooksPath)) {
17132
17565
  return {
17133
17566
  missingManifestPaths,
17134
17567
  missingHookTargets: [],
@@ -17136,13 +17569,13 @@ function findInstalledBundleIntegrityIssues(rootDir, platform) {
17136
17569
  };
17137
17570
  }
17138
17571
  try {
17139
- const hooks = JSON.parse(readFileSync11(hooksPath, "utf-8"));
17572
+ const hooks = JSON.parse(readFileSync12(hooksPath, "utf-8"));
17140
17573
  const commands = [];
17141
17574
  collectHookCommandStrings(hooks, commands);
17142
17575
  const missingHookTargets = [...new Set(
17143
17576
  commands.flatMap(extractBundleCommandTargets).filter((value) => {
17144
17577
  const resolved = resolveBundleReference(rootDir, value);
17145
- return resolved !== void 0 && !existsSync24(resolved);
17578
+ return resolved !== void 0 && !existsSync22(resolved);
17146
17579
  })
17147
17580
  )].sort();
17148
17581
  return {
@@ -17162,9 +17595,9 @@ function findInstalledRuntimeScriptIssues(rootDir, manifest) {
17162
17595
  const mcpReference = typeof manifest.mcpServers === "string" ? manifest.mcpServers : void 0;
17163
17596
  if (!mcpReference) return [];
17164
17597
  const mcpPath = resolveBundleReference(rootDir, mcpReference);
17165
- if (!mcpPath || !existsSync24(mcpPath)) return [];
17598
+ if (!mcpPath || !existsSync22(mcpPath)) return [];
17166
17599
  try {
17167
- const parsed = JSON.parse(readFileSync11(mcpPath, "utf-8"));
17600
+ const parsed = JSON.parse(readFileSync12(mcpPath, "utf-8"));
17168
17601
  const issues = /* @__PURE__ */ new Set();
17169
17602
  for (const [serverName, server] of Object.entries(parsed.mcpServers ?? {})) {
17170
17603
  if (!server || typeof server !== "object") continue;
@@ -17176,8 +17609,8 @@ function findInstalledRuntimeScriptIssues(rootDir, manifest) {
17176
17609
  ].flatMap(extractBundleCommandTargets);
17177
17610
  for (const target of commandTargets) {
17178
17611
  const resolved = resolveBundleReference(rootDir, target);
17179
- if (!resolved || !existsSync24(resolved) || !resolved.endsWith(".sh")) continue;
17180
- const content = readFileSync11(resolved, "utf-8");
17612
+ if (!resolved || !existsSync22(resolved) || !resolved.endsWith(".sh")) continue;
17613
+ const content = readFileSync12(resolved, "utf-8");
17181
17614
  if (!content.includes("check-env.sh")) continue;
17182
17615
  const relativePath = resolved.startsWith(`${rootDir}/`) ? resolved.slice(rootDir.length + 1) : resolved;
17183
17616
  issues.add(`runtime script ${relativePath} for MCP server "${serverName}" still references installer-owned scripts/check-env.sh`);
@@ -17210,20 +17643,20 @@ function assertInstalledBundleIntegrity(rootDir, platform, label) {
17210
17643
  function ensureClaudeMarketplace(pluginName, sourceDir, materialized) {
17211
17644
  const marketplaceName = getClaudeMarketplaceName(pluginName);
17212
17645
  const marketplaceRoot = getClaudeMarketplaceRoot(pluginName);
17213
- const marketplaceManifestDir = resolve17(marketplaceRoot, ".claude-plugin");
17214
- const marketplacePluginDir = resolve17(marketplaceRoot, "plugins", pluginName);
17215
- const pluginManifestPath = resolve17(sourceDir, ".claude-plugin/plugin.json");
17216
- const pluginManifest = JSON.parse(readFileSync11(pluginManifestPath, "utf-8"));
17646
+ const marketplaceManifestDir = resolve18(marketplaceRoot, ".claude-plugin");
17647
+ const marketplacePluginDir = resolve18(marketplaceRoot, "plugins", pluginName);
17648
+ const pluginManifestPath = resolve18(sourceDir, ".claude-plugin/plugin.json");
17649
+ const pluginManifest = JSON.parse(readFileSync12(pluginManifestPath, "utf-8"));
17217
17650
  rmSync3(marketplaceRoot, { recursive: true, force: true });
17218
17651
  mkdirSync4(marketplaceManifestDir, { recursive: true });
17219
- mkdirSync4(resolve17(marketplaceRoot, "plugins"), { recursive: true });
17652
+ mkdirSync4(resolve18(marketplaceRoot, "plugins"), { recursive: true });
17220
17653
  cpSync3(sourceDir, marketplacePluginDir, { recursive: true });
17221
17654
  if (materialized && materialized.entries.length > 0) {
17222
17655
  materializeInstalledPlugin(marketplacePluginDir, "claude-code", materialized.config, materialized.entries);
17223
17656
  }
17224
17657
  assertInstalledBundleIntegrity(marketplacePluginDir, "claude-code", "Claude marketplace bundle");
17225
17658
  writeFileSync4(
17226
- resolve17(marketplaceManifestDir, "marketplace.json"),
17659
+ resolve18(marketplaceManifestDir, "marketplace.json"),
17227
17660
  JSON.stringify({
17228
17661
  name: marketplaceName,
17229
17662
  owner: {
@@ -17263,7 +17696,7 @@ function ensureClaudeMarketplaceRegistered(pluginName, sourceDir, runCommand, ma
17263
17696
  }
17264
17697
  function installClaudePlugin(target, pluginName, runCommand, materialized) {
17265
17698
  const marketplaceName = ensureClaudeMarketplaceRegistered(pluginName, target.sourceDir, runCommand, materialized);
17266
- if (existsSync24(target.pluginDir)) {
17699
+ if (existsSync22(target.pluginDir)) {
17267
17700
  rmSync3(target.pluginDir, { recursive: true, force: true });
17268
17701
  }
17269
17702
  runCommand("claude", ["plugin", "uninstall", `${pluginName}@${marketplaceName}`]);
@@ -17287,9 +17720,9 @@ function uninstallClaudePlugin(target, pluginName, runCommand, options = {}) {
17287
17720
  }
17288
17721
  }
17289
17722
  const marketplaceRoot = getClaudeMarketplaceRoot(pluginName);
17290
- const hadMarketplaceRoot = existsSync24(marketplaceRoot);
17723
+ const hadMarketplaceRoot = existsSync22(marketplaceRoot);
17291
17724
  rmSync3(marketplaceRoot, { recursive: true, force: true });
17292
- const hadLegacyPluginDir = existsSync24(target.pluginDir);
17725
+ const hadLegacyPluginDir = existsSync22(target.pluginDir);
17293
17726
  if (hadLegacyPluginDir) {
17294
17727
  rmSync3(target.pluginDir, { recursive: true, force: true });
17295
17728
  }
@@ -17299,12 +17732,12 @@ function planInstallPlugin(distDir, pluginName, platforms) {
17299
17732
  const targets = getInstallTargets(pluginName);
17300
17733
  const filtered = platforms ? targets.filter((t2) => platforms.includes(t2.platform)) : targets;
17301
17734
  return filtered.map((target) => {
17302
- const sourceDir = resolve17(distDir, target.platform);
17735
+ const sourceDir = resolve18(distDir, target.platform);
17303
17736
  return {
17304
17737
  ...target,
17305
17738
  sourceDir,
17306
- built: existsSync24(sourceDir),
17307
- existing: existsSync24(target.pluginDir)
17739
+ built: existsSync22(sourceDir),
17740
+ existing: existsSync22(target.pluginDir)
17308
17741
  };
17309
17742
  });
17310
17743
  }
@@ -17347,7 +17780,7 @@ async function installPlugin(distDir, pluginName, platforms, options = {}) {
17347
17780
  createSymlinkInstall(target);
17348
17781
  }
17349
17782
  const manifestPath = manifestPathForPlatform(target.platform);
17350
- if (manifestPath && existsSync24(resolve17(target.pluginDir, manifestPath))) {
17783
+ if (manifestPath && existsSync22(resolve18(target.pluginDir, manifestPath))) {
17351
17784
  assertInstalledBundleIntegrity(target.pluginDir, target.platform, `Installed ${target.platform} plugin bundle`);
17352
17785
  }
17353
17786
  if (target.platform === "codex") {
@@ -17367,7 +17800,7 @@ Installed ${installed} plugin(s).`);
17367
17800
  console.log(` 1. Run: pluxx verify-install --target ${filtered.map((target) => target.platform).join(",")}`);
17368
17801
  console.log(" 2. Open the host plugin screen and confirm the plugin appears there.");
17369
17802
  console.log(" 3. Reload or restart the host if it was already open.");
17370
- for (const note of getInstallFollowupNotes(filtered.map((target) => target.platform))) {
17803
+ for (const note of getInstallFollowupNotes2(filtered.map((target) => target.platform))) {
17371
17804
  console.log(note);
17372
17805
  }
17373
17806
  }
@@ -17389,13 +17822,13 @@ async function uninstallPlugin(pluginName, platforms, options = {}) {
17389
17822
  continue;
17390
17823
  }
17391
17824
  let removedTarget = false;
17392
- if (existsSync24(target.pluginDir)) {
17825
+ if (existsSync22(target.pluginDir)) {
17393
17826
  rmSync3(target.pluginDir, { recursive: true, force: true });
17394
17827
  removedTarget = true;
17395
17828
  }
17396
17829
  if (target.platform === "opencode") {
17397
17830
  const entryPath = getOpenCodeEntryPath(target.pluginDir);
17398
- if (existsSync24(entryPath)) {
17831
+ if (existsSync22(entryPath)) {
17399
17832
  rmSync3(entryPath, { force: true });
17400
17833
  removedTarget = true;
17401
17834
  }
@@ -17508,8 +17941,8 @@ function checkReadablePath(checks, rootDir, label, configuredPath, required) {
17508
17941
  }
17509
17942
  return;
17510
17943
  }
17511
- const resolvedPath = resolve18(rootDir, configuredPath);
17512
- if (!existsSync25(resolvedPath)) {
17944
+ const resolvedPath = resolve19(rootDir, configuredPath);
17945
+ if (!existsSync23(resolvedPath)) {
17513
17946
  addCheck2(checks, {
17514
17947
  level: "error",
17515
17948
  code: "path-not-found",
@@ -17919,8 +18352,8 @@ function checkCompilerIntent(checks, rootDir) {
17919
18352
  }
17920
18353
  }
17921
18354
  function checkScaffoldMetadata(checks, rootDir, config) {
17922
- const metadataPath = resolve18(rootDir, MCP_SCAFFOLD_METADATA_PATH);
17923
- if (!existsSync25(metadataPath)) {
18355
+ const metadataPath = resolve19(rootDir, MCP_SCAFFOLD_METADATA_PATH);
18356
+ if (!existsSync23(metadataPath)) {
17924
18357
  addCheck2(checks, {
17925
18358
  level: "info",
17926
18359
  code: "mcp-metadata-missing",
@@ -17932,7 +18365,7 @@ function checkScaffoldMetadata(checks, rootDir, config) {
17932
18365
  return;
17933
18366
  }
17934
18367
  try {
17935
- const metadata = JSON.parse(readFileSync12(metadataPath, "utf-8"));
18368
+ const metadata = JSON.parse(readFileSync13(metadataPath, "utf-8"));
17936
18369
  const invalidManaged = metadata.managedFiles.filter((path) => !isSafeManagedPath(path));
17937
18370
  if (metadata.version !== 1) {
17938
18371
  addCheck2(checks, {
@@ -17985,7 +18418,7 @@ function checkScaffoldMetadata(checks, rootDir, config) {
17985
18418
  }
17986
18419
  }
17987
18420
  function detectConsumerLayout(rootDir) {
17988
- if (existsSync25(resolve18(rootDir, ".claude-plugin/plugin.json"))) {
18421
+ if (existsSync23(resolve19(rootDir, ".claude-plugin/plugin.json"))) {
17989
18422
  return {
17990
18423
  kind: "installed-platform",
17991
18424
  platform: "claude-code",
@@ -17993,7 +18426,7 @@ function detectConsumerLayout(rootDir) {
17993
18426
  mcpConfigPath: ".mcp.json"
17994
18427
  };
17995
18428
  }
17996
- if (existsSync25(resolve18(rootDir, ".cursor-plugin/plugin.json"))) {
18429
+ if (existsSync23(resolve19(rootDir, ".cursor-plugin/plugin.json"))) {
17997
18430
  return {
17998
18431
  kind: "installed-platform",
17999
18432
  platform: "cursor",
@@ -18001,7 +18434,7 @@ function detectConsumerLayout(rootDir) {
18001
18434
  mcpConfigPath: "mcp.json"
18002
18435
  };
18003
18436
  }
18004
- if (existsSync25(resolve18(rootDir, ".codex-plugin/plugin.json"))) {
18437
+ if (existsSync23(resolve19(rootDir, ".codex-plugin/plugin.json"))) {
18005
18438
  return {
18006
18439
  kind: "installed-platform",
18007
18440
  platform: "codex",
@@ -18009,11 +18442,11 @@ function detectConsumerLayout(rootDir) {
18009
18442
  mcpConfigPath: ".mcp.json"
18010
18443
  };
18011
18444
  }
18012
- const packagePath = resolve18(rootDir, "package.json");
18013
- const indexPath = resolve18(rootDir, "index.ts");
18014
- if (existsSync25(packagePath) && existsSync25(indexPath)) {
18445
+ const packagePath = resolve19(rootDir, "package.json");
18446
+ const indexPath = resolve19(rootDir, "index.ts");
18447
+ if (existsSync23(packagePath) && existsSync23(indexPath)) {
18015
18448
  try {
18016
- const pkg = JSON.parse(readFileSync12(packagePath, "utf-8"));
18449
+ const pkg = JSON.parse(readFileSync13(packagePath, "utf-8"));
18017
18450
  if (pkg.peerDependencies?.["@opencode-ai/plugin"] || pkg.keywords?.includes("opencode-plugin")) {
18018
18451
  return {
18019
18452
  kind: "installed-platform",
@@ -18029,16 +18462,16 @@ function detectConsumerLayout(rootDir) {
18029
18462
  };
18030
18463
  }
18031
18464
  }
18032
- if (CONFIG_FILES.some((filename) => existsSync25(resolve18(rootDir, filename)))) {
18465
+ if (CONFIG_FILES.some((filename) => existsSync23(resolve19(rootDir, filename)))) {
18033
18466
  return { kind: "source-project" };
18034
18467
  }
18035
- if (["claude-code", "cursor", "codex", "opencode"].some((dir) => existsSync25(resolve18(rootDir, dir)))) {
18468
+ if (["claude-code", "cursor", "codex", "opencode"].some((dir) => existsSync23(resolve19(rootDir, dir)))) {
18036
18469
  return { kind: "multi-target-dist" };
18037
18470
  }
18038
18471
  return { kind: "unknown" };
18039
18472
  }
18040
18473
  function readJsonFile2(rootDir, relativePath) {
18041
- return JSON.parse(readFileSync12(resolve18(rootDir, relativePath), "utf-8"));
18474
+ return JSON.parse(readFileSync13(resolve19(rootDir, relativePath), "utf-8"));
18042
18475
  }
18043
18476
  function checkConsumerBundlePath(checks, rootDir) {
18044
18477
  try {
@@ -18089,8 +18522,8 @@ function checkConsumerManifest(checks, rootDir, layout) {
18089
18522
  }
18090
18523
  function checkInstalledUserConfig(checks, rootDir) {
18091
18524
  const userConfigPath = ".pluxx-user.json";
18092
- const resolvedPath = resolve18(rootDir, userConfigPath);
18093
- if (!existsSync25(resolvedPath)) {
18525
+ const resolvedPath = resolve19(rootDir, userConfigPath);
18526
+ if (!existsSync23(resolvedPath)) {
18094
18527
  addCheck2(checks, {
18095
18528
  level: "info",
18096
18529
  code: "consumer-user-config-missing",
@@ -18102,7 +18535,7 @@ function checkInstalledUserConfig(checks, rootDir) {
18102
18535
  return;
18103
18536
  }
18104
18537
  try {
18105
- const payload = JSON.parse(readFileSync12(resolvedPath, "utf-8"));
18538
+ const payload = JSON.parse(readFileSync13(resolvedPath, "utf-8"));
18106
18539
  const valueCount = Object.keys(payload.values ?? {}).length;
18107
18540
  const envCount = Object.keys(payload.env ?? {}).length;
18108
18541
  const placeholderKeys = [
@@ -18140,8 +18573,8 @@ function checkInstalledUserConfig(checks, rootDir) {
18140
18573
  }
18141
18574
  function checkInstalledEnvValidation(checks, rootDir) {
18142
18575
  const envScriptPath = INSTALLER_OWNED_CHECK_ENV_PATH;
18143
- const resolvedPath = resolve18(rootDir, envScriptPath);
18144
- if (!existsSync25(resolvedPath)) {
18576
+ const resolvedPath = resolve19(rootDir, envScriptPath);
18577
+ if (!existsSync23(resolvedPath)) {
18145
18578
  addCheck2(checks, {
18146
18579
  level: "info",
18147
18580
  code: "consumer-env-script-missing",
@@ -18152,7 +18585,7 @@ function checkInstalledEnvValidation(checks, rootDir) {
18152
18585
  });
18153
18586
  return;
18154
18587
  }
18155
- const content = readFileSync12(resolvedPath, "utf-8");
18588
+ const content = readFileSync13(resolvedPath, "utf-8");
18156
18589
  if (content.includes(MATERIALIZED_ENV_MARKER)) {
18157
18590
  addCheck2(checks, {
18158
18591
  level: "success",
@@ -18180,7 +18613,7 @@ function checkInstalledRuntimeScriptRoles(checks, rootDir) {
18180
18613
  "scripts/bootstrap-runtime.sh",
18181
18614
  "scripts/start-mcp.sh"
18182
18615
  ];
18183
- const presentRoles = roleFiles.filter((relativePath) => existsSync25(resolve18(rootDir, relativePath))).map((relativePath) => getRuntimeScriptRoleForPath(relativePath)).filter((role) => role !== null);
18616
+ const presentRoles = roleFiles.filter((relativePath) => existsSync23(resolve19(rootDir, relativePath))).map((relativePath) => getRuntimeScriptRoleForPath(relativePath)).filter((role) => role !== null);
18184
18617
  if (presentRoles.length === 0) return;
18185
18618
  addCheck2(checks, {
18186
18619
  level: "info",
@@ -18203,8 +18636,8 @@ async function checkInstalledMcpConfig(checks, rootDir, layout) {
18203
18636
  });
18204
18637
  return;
18205
18638
  }
18206
- const resolvedPath = resolve18(rootDir, layout.mcpConfigPath);
18207
- if (!existsSync25(resolvedPath)) {
18639
+ const resolvedPath = resolve19(rootDir, layout.mcpConfigPath);
18640
+ if (!existsSync23(resolvedPath)) {
18208
18641
  addCheck2(checks, {
18209
18642
  level: "info",
18210
18643
  code: "consumer-mcp-config-missing",
@@ -18375,8 +18808,8 @@ function findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries) {
18375
18808
  const args2 = Array.isArray(server.args) ? server.args.filter((value) => typeof value === "string") : [];
18376
18809
  for (const candidate of [command2, ...args2]) {
18377
18810
  if (!candidate || !isLikelyLocalRuntimePath3(candidate)) continue;
18378
- const resolvedPath = resolve18(rootDir, candidate);
18379
- if (!existsSync25(resolvedPath)) {
18811
+ const resolvedPath = resolve19(rootDir, candidate);
18812
+ if (!existsSync23(resolvedPath)) {
18380
18813
  missing.add(candidate);
18381
18814
  }
18382
18815
  }
@@ -18487,7 +18920,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
18487
18920
  const pluginName = basename6(rootDir);
18488
18921
  const entryPath = `${rootDir}.ts`;
18489
18922
  const entryRelativePath = `${pluginName}.ts`;
18490
- if (!existsSync25(entryPath)) {
18923
+ if (!existsSync23(entryPath)) {
18491
18924
  addCheck2(checks, {
18492
18925
  level: "error",
18493
18926
  code: "consumer-opencode-entry-missing",
@@ -18498,7 +18931,7 @@ function checkInstalledOpenCodeHostBridge(checks, rootDir) {
18498
18931
  });
18499
18932
  return;
18500
18933
  }
18501
- const entryContent = readFileSync12(entryPath, "utf-8");
18934
+ const entryContent = readFileSync13(entryPath, "utf-8");
18502
18935
  const expectedImport = `import * as PluginModule from "./${pluginName}/index.ts"`;
18503
18936
  const expectedBridge = `directory: join(context.directory, "${pluginName}")`;
18504
18937
  if (!entryContent.includes(expectedImport) || !entryContent.includes(expectedBridge)) {
@@ -18526,8 +18959,8 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
18526
18959
  return;
18527
18960
  }
18528
18961
  const pluginName = basename6(rootDir);
18529
- const sourceSkillsDir = resolve18(rootDir, "skills");
18530
- if (!existsSync25(sourceSkillsDir)) {
18962
+ const sourceSkillsDir = resolve19(rootDir, "skills");
18963
+ if (!existsSync23(sourceSkillsDir)) {
18531
18964
  addCheck2(checks, {
18532
18965
  level: "info",
18533
18966
  code: "consumer-opencode-skill-sync-not-applicable",
@@ -18538,21 +18971,21 @@ function checkInstalledOpenCodeSkills(checks, rootDir) {
18538
18971
  });
18539
18972
  return;
18540
18973
  }
18541
- const skillRoot = resolve18(dirname7(dirname7(rootDir)), "skills");
18974
+ const skillRoot = resolve19(dirname7(dirname7(rootDir)), "skills");
18542
18975
  const missingSkills = [];
18543
18976
  const malformedSkills = [];
18544
18977
  let expectedSkillCount = 0;
18545
18978
  for (const entry of readdirSync10(sourceSkillsDir, { withFileTypes: true })) {
18546
18979
  if (!entry.isDirectory()) continue;
18547
- const sourceSkillPath = resolve18(sourceSkillsDir, entry.name, "SKILL.md");
18548
- if (!existsSync25(sourceSkillPath)) continue;
18980
+ const sourceSkillPath = resolve19(sourceSkillsDir, entry.name, "SKILL.md");
18981
+ if (!existsSync23(sourceSkillPath)) continue;
18549
18982
  expectedSkillCount++;
18550
- const installedSkillPath = resolve18(skillRoot, `${pluginName}-${entry.name}`, "SKILL.md");
18551
- if (!existsSync25(installedSkillPath)) {
18983
+ const installedSkillPath = resolve19(skillRoot, `${pluginName}-${entry.name}`, "SKILL.md");
18984
+ if (!existsSync23(installedSkillPath)) {
18552
18985
  missingSkills.push(`${pluginName}-${entry.name}`);
18553
18986
  continue;
18554
18987
  }
18555
- const installedContent = readFileSync12(installedSkillPath, "utf-8");
18988
+ const installedContent = readFileSync13(installedSkillPath, "utf-8");
18556
18989
  if (!installedContent.includes(`${pluginName}/`)) {
18557
18990
  malformedSkills.push(`${pluginName}-${entry.name}`);
18558
18991
  }
@@ -18647,7 +19080,7 @@ async function doctorConsumer(rootDir = process.cwd()) {
18647
19080
  }
18648
19081
  async function doctorProject(rootDir = process.cwd()) {
18649
19082
  const checks = [];
18650
- const configPath = CONFIG_FILES.find((filename) => existsSync25(resolve18(rootDir, filename)));
19083
+ const configPath = CONFIG_FILES.find((filename) => existsSync23(resolve19(rootDir, filename)));
18651
19084
  addRuntimeChecks(checks, "project");
18652
19085
  if (!configPath) {
18653
19086
  addCheck2(checks, {
@@ -18748,7 +19181,7 @@ function printDoctorReport(report) {
18748
19181
 
18749
19182
  // src/cli/dev.ts
18750
19183
  import { watch } from "fs";
18751
- import { relative as relative11, resolve as resolve19 } from "path";
19184
+ import { relative as relative11, resolve as resolve20 } from "path";
18752
19185
  var WATCH_PATTERNS = [
18753
19186
  /^pluxx\.config\.(ts|js|json)$/,
18754
19187
  /^skills\//,
@@ -18774,7 +19207,7 @@ async function runDev(args2) {
18774
19207
  let pendingFile = null;
18775
19208
  const watcher = watch(rootDir, { recursive: true }, (_event, filename) => {
18776
19209
  if (!filename) return;
18777
- const rel = relative11(rootDir, resolve19(rootDir, filename));
19210
+ const rel = relative11(rootDir, resolve20(rootDir, filename));
18778
19211
  if (rel.startsWith("dist/") || rel.startsWith(".") || rel.includes("node_modules")) {
18779
19212
  return;
18780
19213
  }
@@ -18829,8 +19262,8 @@ async function runBuild(rootDir, targets) {
18829
19262
  }
18830
19263
 
18831
19264
  // src/cli/migrate.ts
18832
- import { basename as basename7, relative as relative12, resolve as resolve20 } from "path";
18833
- import { existsSync as existsSync26, readdirSync as readdirSync11, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync13, writeFileSync as writeFileSync5 } from "fs";
19265
+ import { basename as basename7, relative as relative12, resolve as resolve21 } from "path";
19266
+ import { existsSync as existsSync24, readdirSync as readdirSync11, mkdirSync as mkdirSync5, cpSync as cpSync4, readFileSync as readFileSync14, writeFileSync as writeFileSync5 } from "fs";
18834
19267
  function detectPlatform(pluginDir) {
18835
19268
  const checks = [
18836
19269
  { dir: ".claude-plugin", platform: "claude-code" },
@@ -18838,15 +19271,15 @@ function detectPlatform(pluginDir) {
18838
19271
  { dir: ".codex-plugin", platform: "codex" }
18839
19272
  ];
18840
19273
  for (const check of checks) {
18841
- const manifestPath = resolve20(pluginDir, check.dir, "plugin.json");
18842
- if (existsSync26(manifestPath)) {
19274
+ const manifestPath = resolve21(pluginDir, check.dir, "plugin.json");
19275
+ if (existsSync24(manifestPath)) {
18843
19276
  return { platform: check.platform, manifestPath };
18844
19277
  }
18845
19278
  }
18846
- const pkgPath = resolve20(pluginDir, "package.json");
18847
- if (existsSync26(pkgPath)) {
19279
+ const pkgPath = resolve21(pluginDir, "package.json");
19280
+ if (existsSync24(pkgPath)) {
18848
19281
  try {
18849
- const pkg = JSON.parse(readFileSync13(pkgPath, "utf-8"));
19282
+ const pkg = JSON.parse(readFileSync14(pkgPath, "utf-8"));
18850
19283
  const deps = {
18851
19284
  ...pkg.dependencies,
18852
19285
  ...pkg.devDependencies,
@@ -18861,7 +19294,7 @@ function detectPlatform(pluginDir) {
18861
19294
  return null;
18862
19295
  }
18863
19296
  function parseManifest(detection) {
18864
- const raw = JSON.parse(readFileSync13(detection.manifestPath, "utf-8"));
19297
+ const raw = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
18865
19298
  const result = {};
18866
19299
  if (raw.name) result.name = raw.name;
18867
19300
  if (raw.version) result.version = raw.version;
@@ -18886,20 +19319,20 @@ function parseManifest(detection) {
18886
19319
  }
18887
19320
  function parseMcp(pluginDir, detection) {
18888
19321
  const mcpPaths = [
18889
- resolve20(pluginDir, ".mcp.json"),
18890
- resolve20(pluginDir, "mcp.json")
19322
+ resolve21(pluginDir, ".mcp.json"),
19323
+ resolve21(pluginDir, "mcp.json")
18891
19324
  ];
18892
19325
  try {
18893
- const manifest = JSON.parse(readFileSync13(detection.manifestPath, "utf-8"));
19326
+ const manifest = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
18894
19327
  if (manifest.mcpServers && typeof manifest.mcpServers === "string") {
18895
- mcpPaths.unshift(resolve20(pluginDir, manifest.mcpServers));
19328
+ mcpPaths.unshift(resolve21(pluginDir, manifest.mcpServers));
18896
19329
  }
18897
19330
  } catch {
18898
19331
  }
18899
19332
  for (const mcpPath of mcpPaths) {
18900
- if (!existsSync26(mcpPath)) continue;
19333
+ if (!existsSync24(mcpPath)) continue;
18901
19334
  try {
18902
- const raw = JSON.parse(readFileSync13(mcpPath, "utf-8"));
19335
+ const raw = JSON.parse(readFileSync14(mcpPath, "utf-8"));
18903
19336
  const servers = raw.mcpServers ?? raw;
18904
19337
  if (!servers || typeof servers !== "object") continue;
18905
19338
  const result = {};
@@ -18979,21 +19412,21 @@ var HOOK_EVENT_MAP = {
18979
19412
  };
18980
19413
  function parseHooks(pluginDir, detection) {
18981
19414
  const hooksPaths = [
18982
- resolve20(pluginDir, ".codex", "hooks.json"),
18983
- resolve20(pluginDir, "hooks.json"),
18984
- resolve20(pluginDir, "hooks", "hooks.json")
19415
+ resolve21(pluginDir, ".codex", "hooks.json"),
19416
+ resolve21(pluginDir, "hooks.json"),
19417
+ resolve21(pluginDir, "hooks", "hooks.json")
18985
19418
  ];
18986
19419
  try {
18987
- const manifest = JSON.parse(readFileSync13(detection.manifestPath, "utf-8"));
19420
+ const manifest = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
18988
19421
  if (manifest.hooks && typeof manifest.hooks === "string") {
18989
- hooksPaths.unshift(resolve20(pluginDir, manifest.hooks));
19422
+ hooksPaths.unshift(resolve21(pluginDir, manifest.hooks));
18990
19423
  }
18991
19424
  } catch {
18992
19425
  }
18993
19426
  for (const hooksPath of hooksPaths) {
18994
- if (!existsSync26(hooksPath)) continue;
19427
+ if (!existsSync24(hooksPath)) continue;
18995
19428
  try {
18996
- const raw = JSON.parse(readFileSync13(hooksPath, "utf-8"));
19429
+ const raw = JSON.parse(readFileSync14(hooksPath, "utf-8"));
18997
19430
  const hooksObj = raw.hooks ?? raw;
18998
19431
  if (!hooksObj || typeof hooksObj !== "object") continue;
18999
19432
  const result = {};
@@ -19039,8 +19472,8 @@ function findInstructions(pluginDir) {
19039
19472
  "README.md"
19040
19473
  ];
19041
19474
  for (const candidate of candidates) {
19042
- const filePath = resolve20(pluginDir, candidate);
19043
- if (existsSync26(filePath)) {
19475
+ const filePath = resolve21(pluginDir, candidate);
19476
+ if (existsSync24(filePath)) {
19044
19477
  return `./${candidate}`;
19045
19478
  }
19046
19479
  }
@@ -19065,7 +19498,7 @@ function detectCanonicalSourcePaths(pluginDir) {
19065
19498
  for (const bucket of Object.keys(CANONICAL_SOURCE_CANDIDATES)) {
19066
19499
  for (const candidate of CANONICAL_SOURCE_CANDIDATES[bucket]) {
19067
19500
  const normalized = stripRelativePrefix(candidate);
19068
- if (!existsSync26(resolve20(pluginDir, normalized))) continue;
19501
+ if (!existsSync24(resolve21(pluginDir, normalized))) continue;
19069
19502
  result[bucket] = normalizeRelativeDir(normalized);
19070
19503
  break;
19071
19504
  }
@@ -19080,8 +19513,8 @@ function detectPassthroughDirs(pluginDir, mcp) {
19080
19513
  const match = part.match(/\$\{[A-Z_]*PLUGIN_ROOT\}\/([^/]+)/);
19081
19514
  if (!match?.[1]) continue;
19082
19515
  const dirName = match[1];
19083
- const dirPath = resolve20(pluginDir, dirName);
19084
- if (existsSync26(dirPath)) {
19516
+ const dirPath = resolve21(pluginDir, dirName);
19517
+ if (existsSync24(dirPath)) {
19085
19518
  passthrough.add(`./${dirName}/`);
19086
19519
  }
19087
19520
  }
@@ -19116,7 +19549,7 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
19116
19549
  if (!sourcePaths.skills) {
19117
19550
  return { notes: [], skillPolicies: [] };
19118
19551
  }
19119
- const skillsDir = resolve20(pluginDir, stripRelativePrefix(sourcePaths.skills));
19552
+ const skillsDir = resolve21(pluginDir, stripRelativePrefix(sourcePaths.skills));
19120
19553
  const entries = readdirSync11(skillsDir, { withFileTypes: true });
19121
19554
  const allow = /* @__PURE__ */ new Set();
19122
19555
  const notes = [];
@@ -19124,19 +19557,33 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
19124
19557
  let sawAllowedTools = false;
19125
19558
  for (const entry of entries) {
19126
19559
  if (!entry.isDirectory()) continue;
19127
- const skillPath = resolve20(skillsDir, entry.name, "SKILL.md");
19128
- if (!existsSync26(skillPath)) continue;
19129
- const skill = parseSkillMarkdown(readFileSync13(skillPath, "utf-8"));
19560
+ const skillPath = resolve21(skillsDir, entry.name, "SKILL.md");
19561
+ if (!existsSync24(skillPath)) continue;
19562
+ const skill = parseSkillMarkdown(readFileSync14(skillPath, "utf-8"));
19130
19563
  const inferredRules = skill.allowedTools.map(normalizeMigratedAllowedTool).filter((rule) => Boolean(rule));
19131
19564
  if (inferredRules.length === 0) continue;
19132
19565
  sawAllowedTools = true;
19133
19566
  for (const rule of inferredRules) {
19134
19567
  allow.add(rule);
19135
19568
  }
19569
+ const sourceFrontmatter = {};
19570
+ if (skill.whenToUse) sourceFrontmatter.when_to_use = skill.whenToUse;
19571
+ if (skill.argumentHint) sourceFrontmatter["argument-hint"] = skill.argumentHint;
19572
+ if (skill.arguments.length > 0) sourceFrontmatter.arguments = skill.arguments;
19573
+ if (typeof skill.disableModelInvocation === "boolean") sourceFrontmatter["disable-model-invocation"] = skill.disableModelInvocation;
19574
+ if (typeof skill.userInvocable === "boolean") sourceFrontmatter["user-invocable"] = skill.userInvocable;
19575
+ if (skill.model) sourceFrontmatter.model = skill.model;
19576
+ if (skill.effort) sourceFrontmatter.effort = skill.effort;
19577
+ if (skill.context) sourceFrontmatter.context = skill.context;
19578
+ if (skill.agent) sourceFrontmatter.agent = skill.agent;
19579
+ if (skill.hooks !== void 0) sourceFrontmatter.hooks = skill.hooks;
19580
+ if (skill.paths.length > 0) sourceFrontmatter.paths = skill.paths;
19581
+ if (skill.shell) sourceFrontmatter.shell = skill.shell;
19136
19582
  skillPolicies.push({
19137
19583
  skillDir: entry.name,
19138
19584
  title: skill.name ?? skill.firstHeading ?? titleCaseFromDirName(entry.name),
19139
19585
  description: skill.description,
19586
+ ...Object.keys(sourceFrontmatter).length > 0 ? { sourceFrontmatter } : {},
19140
19587
  source: {
19141
19588
  kind: "claude-allowed-tools",
19142
19589
  platform: "claude-code"
@@ -19151,6 +19598,7 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
19151
19598
  }
19152
19599
  notes.push("Inferred from Claude-style allowed-tools frontmatter.");
19153
19600
  notes.push(`Preserved skill-scoped tool access in ${PLUXX_COMPILER_INTENT_PATH} and flattened it into plugin-level canonical permissions as a fallback.`);
19601
+ notes.push(`Preserved richer Claude skill frontmatter in ${PLUXX_COMPILER_INTENT_PATH} so migrate does not collapse author intent back into opaque markdown.`);
19154
19602
  return {
19155
19603
  permissions: {
19156
19604
  allow: [...allow].sort()
@@ -19162,7 +19610,7 @@ function inferPermissionsFromMigratedSkills(pluginDir, sourcePaths) {
19162
19610
  function readMigratedSkills(pluginDir, sourcePaths) {
19163
19611
  const skills = [];
19164
19612
  if (sourcePaths.skills) {
19165
- const skillsDir = resolve20(pluginDir, stripRelativePrefix(sourcePaths.skills));
19613
+ const skillsDir = resolve21(pluginDir, stripRelativePrefix(sourcePaths.skills));
19166
19614
  for (const skill of readCanonicalSkillFiles(skillsDir)) {
19167
19615
  skills.push({
19168
19616
  dirName: skill.dirName,
@@ -19173,12 +19621,12 @@ function readMigratedSkills(pluginDir, sourcePaths) {
19173
19621
  }
19174
19622
  }
19175
19623
  if (skills.length === 0 && sourcePaths.commands) {
19176
- const commandsDir = resolve20(pluginDir, stripRelativePrefix(sourcePaths.commands));
19624
+ const commandsDir = resolve21(pluginDir, stripRelativePrefix(sourcePaths.commands));
19177
19625
  const entries = readdirSync11(commandsDir, { withFileTypes: true });
19178
19626
  for (const entry of entries) {
19179
19627
  if (!entry.isFile() || !entry.name.endsWith(".md")) continue;
19180
19628
  const dirName = toKebabCase2(entry.name.replace(/\.md$/, "")) || "command";
19181
- const content = readFileSync13(resolve20(commandsDir, entry.name), "utf-8");
19629
+ const content = readFileSync14(resolve21(commandsDir, entry.name), "utf-8");
19182
19630
  const parsed = parseSkillMarkdown(content);
19183
19631
  skills.push({
19184
19632
  dirName,
@@ -19194,14 +19642,14 @@ var HOST_NATIVE_SKILL_FRONTMATTER_KEYS = /* @__PURE__ */ new Set([
19194
19642
  "allowed-tools"
19195
19643
  ]);
19196
19644
  function sanitizeMigratedSkillFrontmatter(outputDir) {
19197
- if (!existsSync26(resolve20(outputDir, "skills"))) return;
19198
- const skillsDir = resolve20(outputDir, "skills");
19645
+ if (!existsSync24(resolve21(outputDir, "skills"))) return;
19646
+ const skillsDir = resolve21(outputDir, "skills");
19199
19647
  const entries = readdirSync11(skillsDir, { withFileTypes: true });
19200
19648
  for (const entry of entries) {
19201
19649
  if (!entry.isDirectory()) continue;
19202
- const skillPath = resolve20(skillsDir, entry.name, "SKILL.md");
19203
- if (!existsSync26(skillPath)) continue;
19204
- const parsed = parseSkillMarkdown(readFileSync13(skillPath, "utf-8"));
19650
+ const skillPath = resolve21(skillsDir, entry.name, "SKILL.md");
19651
+ if (!existsSync24(skillPath)) continue;
19652
+ const parsed = parseSkillMarkdown(readFileSync14(skillPath, "utf-8"));
19205
19653
  if (!parsed.hasValidFrontmatter) continue;
19206
19654
  const sanitized = parsed.frontmatterLines.filter((line) => {
19207
19655
  const match = /^([A-Za-z0-9_-]+)\s*:/.exec(line.trim());
@@ -19293,7 +19741,7 @@ function hasTopLevelFrontmatterKey(frontmatterLines, key) {
19293
19741
  });
19294
19742
  }
19295
19743
  function normalizeMigratedOpenCodeAgentFile(agentPath) {
19296
- const original = readFileSync13(agentPath, "utf-8");
19744
+ const original = readFileSync14(agentPath, "utf-8");
19297
19745
  const parsed = splitMarkdownFrontmatter5(original);
19298
19746
  const fileStem = toKebabCase2(basename7(agentPath, ".md")) || "agent";
19299
19747
  const fallbackDescription = buildFallbackAgentDescription(fileStem);
@@ -19335,7 +19783,7 @@ function walkMarkdownFiles3(dir) {
19335
19783
  const entries = readdirSync11(dir, { withFileTypes: true });
19336
19784
  const files = [];
19337
19785
  for (const entry of entries) {
19338
- const fullPath = resolve20(dir, entry.name);
19786
+ const fullPath = resolve21(dir, entry.name);
19339
19787
  if (entry.isDirectory()) {
19340
19788
  files.push(...walkMarkdownFiles3(fullPath));
19341
19789
  continue;
@@ -19347,7 +19795,7 @@ function walkMarkdownFiles3(dir) {
19347
19795
  return files;
19348
19796
  }
19349
19797
  function normalizeMigratedOpenCodeAgents(destDir) {
19350
- if (!existsSync26(destDir)) return [];
19798
+ if (!existsSync24(destDir)) return [];
19351
19799
  const normalized = [];
19352
19800
  for (const filePath of walkMarkdownFiles3(destDir)) {
19353
19801
  if (normalizeMigratedOpenCodeAgentFile(filePath)) {
@@ -19362,11 +19810,11 @@ function copyCodexAgents(sourceDir, destDir) {
19362
19810
  if (tomlEntries.length === 0) return false;
19363
19811
  mkdirSync5(destDir, { recursive: true });
19364
19812
  for (const entry of tomlEntries) {
19365
- const sourcePath = resolve20(sourceDir, entry.name);
19366
- const parsed = parseCodexAgentToml(readFileSync13(sourcePath, "utf-8"));
19813
+ const sourcePath = resolve21(sourceDir, entry.name);
19814
+ const parsed = parseCodexAgentToml(readFileSync14(sourcePath, "utf-8"));
19367
19815
  const fallbackName = entry.name.replace(/\.toml$/, "");
19368
19816
  const fileName = `${toKebabCase2(parsed.name ?? fallbackName) || "agent"}.md`;
19369
- writeFileSync5(resolve20(destDir, fileName), renderMigratedAgentMarkdown(fallbackName, parsed), "utf-8");
19817
+ writeFileSync5(resolve21(destDir, fileName), renderMigratedAgentMarkdown(fallbackName, parsed), "utf-8");
19370
19818
  }
19371
19819
  return true;
19372
19820
  }
@@ -19435,13 +19883,13 @@ function buildMigratedScaffoldMetadata(result, outputDir) {
19435
19883
  ...["skills", "commands", "agents", "scripts", "assets"].flatMap((dir) => {
19436
19884
  if (!result.sourcePaths[dir]) return [];
19437
19885
  const baseDir = dir;
19438
- const dirPath = resolve20(outputDir, baseDir);
19439
- if (!existsSync26(dirPath)) return [];
19886
+ const dirPath = resolve21(outputDir, baseDir);
19887
+ if (!existsSync24(dirPath)) return [];
19440
19888
  const entries = readdirSync11(dirPath, { withFileTypes: true });
19441
19889
  const files = [];
19442
19890
  for (const entry of entries) {
19443
19891
  if (entry.isDirectory()) {
19444
- const nestedDir = resolve20(dirPath, entry.name);
19892
+ const nestedDir = resolve21(dirPath, entry.name);
19445
19893
  for (const nested of readdirSync11(nestedDir, { withFileTypes: true })) {
19446
19894
  if (nested.isFile()) {
19447
19895
  files.push(`${baseDir}/${entry.name}/${nested.name}`);
@@ -19501,9 +19949,9 @@ function copyDirectories(pluginDir, outputDir, sourcePaths, passthrough) {
19501
19949
  const sourcePath = sourcePaths[dir];
19502
19950
  if (!sourcePath) continue;
19503
19951
  const normalizedSource = stripRelativePrefix(sourcePath);
19504
- const src = resolve20(pluginDir, normalizedSource);
19505
- const dest = resolve20(outputDir, dir);
19506
- if (existsSync26(dest)) {
19952
+ const src = resolve21(pluginDir, normalizedSource);
19953
+ const dest = resolve21(outputDir, dir);
19954
+ if (existsSync24(dest)) {
19507
19955
  console.log(` skip ./${dir}/ (already exists)`);
19508
19956
  continue;
19509
19957
  }
@@ -19518,9 +19966,9 @@ function copyDirectories(pluginDir, outputDir, sourcePaths, passthrough) {
19518
19966
  }
19519
19967
  for (const entry of passthrough) {
19520
19968
  const normalized = entry.replace(/^\.\//, "").replace(/\/$/, "");
19521
- const src = resolve20(pluginDir, normalized);
19522
- const dest = resolve20(outputDir, normalized);
19523
- if (existsSync26(dest)) {
19969
+ const src = resolve21(pluginDir, normalized);
19970
+ const dest = resolve21(outputDir, normalized);
19971
+ if (existsSync24(dest)) {
19524
19972
  console.log(` skip ./${normalized}/ (already exists)`);
19525
19973
  continue;
19526
19974
  }
@@ -19664,9 +20112,9 @@ function quote(s) {
19664
20112
  return `'${s.replace(/'/g, "\\'")}'`;
19665
20113
  }
19666
20114
  async function migrate(inputPath) {
19667
- const pluginDir = resolve20(inputPath);
20115
+ const pluginDir = resolve21(inputPath);
19668
20116
  const outputDir = process.cwd();
19669
- if (!existsSync26(pluginDir)) {
20117
+ if (!existsSync24(pluginDir)) {
19670
20118
  console.error(`Error: Path does not exist: ${pluginDir}`);
19671
20119
  process.exit(1);
19672
20120
  }
@@ -19732,8 +20180,8 @@ async function migrate(inputPath) {
19732
20180
  } : {}
19733
20181
  };
19734
20182
  const configContent = generateConfigTs(result);
19735
- const configPath = resolve20(outputDir, "pluxx.config.ts");
19736
- if (existsSync26(configPath)) {
20183
+ const configPath = resolve21(outputDir, "pluxx.config.ts");
20184
+ if (existsSync24(configPath)) {
19737
20185
  console.error(`
19738
20186
  Error: pluxx.config.ts already exists in ${outputDir}`);
19739
20187
  console.error("Remove it first or run from a different directory.");
@@ -19748,22 +20196,22 @@ Generated pluxx.config.ts`);
19748
20196
  }
19749
20197
  sanitizeMigratedSkillFrontmatter(outputDir);
19750
20198
  if (instructions) {
19751
- const srcInstr = resolve20(pluginDir, instructions);
19752
- const destInstr = resolve20(outputDir, instructions);
19753
- if (!existsSync26(destInstr)) {
19754
- const content = readFileSync13(srcInstr, "utf-8");
20199
+ const srcInstr = resolve21(pluginDir, instructions);
20200
+ const destInstr = resolve21(outputDir, instructions);
20201
+ if (!existsSync24(destInstr)) {
20202
+ const content = readFileSync14(srcInstr, "utf-8");
19755
20203
  await writeTextFile(destInstr, content);
19756
20204
  console.log(`Copied: ${instructions}`);
19757
20205
  }
19758
20206
  }
19759
- const taxonomyPath = resolve20(outputDir, MCP_TAXONOMY_PATH);
19760
- const metadataPath = resolve20(outputDir, MCP_SCAFFOLD_METADATA_PATH);
19761
- mkdirSync5(resolve20(outputDir, ".pluxx"), { recursive: true });
20207
+ const taxonomyPath = resolve21(outputDir, MCP_TAXONOMY_PATH);
20208
+ const metadataPath = resolve21(outputDir, MCP_SCAFFOLD_METADATA_PATH);
20209
+ mkdirSync5(resolve21(outputDir, ".pluxx"), { recursive: true });
19762
20210
  await writeTextFile(taxonomyPath, `${JSON.stringify(result.persistedSkills, null, 2)}
19763
20211
  `);
19764
20212
  if (result.compilerIntent) {
19765
20213
  await writeTextFile(
19766
- resolve20(outputDir, PLUXX_COMPILER_INTENT_PATH),
20214
+ resolve21(outputDir, PLUXX_COMPILER_INTENT_PATH),
19767
20215
  `${JSON.stringify(result.compilerIntent, null, 2)}
19768
20216
  `
19769
20217
  );
@@ -19785,8 +20233,8 @@ Generated pluxx.config.ts`);
19785
20233
  }
19786
20234
 
19787
20235
  // src/cli/mcp-proxy.ts
19788
- import { mkdirSync as mkdirSync6, readFileSync as readFileSync14 } from "fs";
19789
- import { dirname as dirname8, resolve as resolve21 } from "path";
20236
+ import { mkdirSync as mkdirSync6, readFileSync as readFileSync15 } from "fs";
20237
+ import { dirname as dirname8, resolve as resolve22 } from "path";
19790
20238
  import * as readline3 from "readline";
19791
20239
  function usage() {
19792
20240
  return [
@@ -19854,15 +20302,15 @@ function sendError(output, id, code, message) {
19854
20302
  });
19855
20303
  }
19856
20304
  async function loadReplayTape(filepath) {
19857
- const absolutePath = resolve21(process.cwd(), filepath);
19858
- const tape = JSON.parse(readFileSync14(absolutePath, "utf-8"));
20305
+ const absolutePath = resolve22(process.cwd(), filepath);
20306
+ const tape = JSON.parse(readFileSync15(absolutePath, "utf-8"));
19859
20307
  if (tape.version !== 1 || !Array.isArray(tape.interactions)) {
19860
20308
  throw new Error(`Replay tape is not a valid pluxx MCP tape: ${filepath}`);
19861
20309
  }
19862
20310
  return tape;
19863
20311
  }
19864
20312
  async function writeTape(filepath, tape) {
19865
- const absolutePath = resolve21(process.cwd(), filepath);
20313
+ const absolutePath = resolve22(process.cwd(), filepath);
19866
20314
  mkdirSync6(dirname8(absolutePath), { recursive: true });
19867
20315
  await writeTextFile(absolutePath, `${JSON.stringify(tape, null, 2)}
19868
20316
  `);
@@ -20034,7 +20482,7 @@ var PromptCancelledError = class extends Error {
20034
20482
  }
20035
20483
  };
20036
20484
  function ask(question) {
20037
- return new Promise((resolve27, reject) => {
20485
+ return new Promise((resolve28, reject) => {
20038
20486
  const rl = readline4.createInterface({
20039
20487
  input: process.stdin,
20040
20488
  output: process.stdout
@@ -20062,7 +20510,7 @@ function ask(question) {
20062
20510
  rl.once("close", onClose);
20063
20511
  rl.question(question, (answer) => {
20064
20512
  settle(() => {
20065
- resolve27(answer);
20513
+ resolve28(answer);
20066
20514
  rl.close();
20067
20515
  });
20068
20516
  });
@@ -21037,15 +21485,15 @@ ${c2}
21037
21485
  } }).prompt();
21038
21486
 
21039
21487
  // src/cli/index.ts
21040
- import { basename as basename8, resolve as resolve26 } from "path";
21488
+ import { basename as basename8, resolve as resolve27 } from "path";
21041
21489
  import { mkdir as mkdir4, mkdtemp as mkdtemp3, rm as rm4 } from "fs/promises";
21042
21490
  import { tmpdir as tmpdir5 } from "os";
21043
21491
  import { spawn as spawn5, spawnSync as spawnSync3 } from "child_process";
21044
21492
 
21045
21493
  // src/cli/publish.ts
21046
- import { chmodSync, existsSync as existsSync27, mkdtempSync as mkdtempSync2, readFileSync as readFileSync15, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
21494
+ import { chmodSync, existsSync as existsSync25, mkdtempSync as mkdtempSync2, readFileSync as readFileSync16, rmSync as rmSync4, writeFileSync as writeFileSync6 } from "fs";
21047
21495
  import { createHash } from "crypto";
21048
- import { resolve as resolve22 } from "path";
21496
+ import { resolve as resolve23 } from "path";
21049
21497
  import { spawnSync as spawnSync2 } from "child_process";
21050
21498
  import { tmpdir as tmpdir3 } from "os";
21051
21499
  var INSTALLER_TARGETS = ["claude-code", "cursor", "codex", "opencode"];
@@ -21078,7 +21526,7 @@ function resolveRequestedChannels(options) {
21078
21526
  }
21079
21527
  function getBuiltTargets(rootDir, config) {
21080
21528
  return config.targets.filter(
21081
- (platform) => existsSync27(resolve22(rootDir, config.outDir, platform))
21529
+ (platform) => existsSync25(resolve23(rootDir, config.outDir, platform))
21082
21530
  );
21083
21531
  }
21084
21532
  function getArchiveAssetName(pluginName, platform, version, variant) {
@@ -21090,7 +21538,7 @@ function getInstallerScriptName(platform) {
21090
21538
  function buildReleaseAssets(rootDir, config, version, targets) {
21091
21539
  const assets = [];
21092
21540
  for (const platform of targets) {
21093
- const bundlePath = resolve22(rootDir, config.outDir, platform);
21541
+ const bundlePath = resolve23(rootDir, config.outDir, platform);
21094
21542
  assets.push({
21095
21543
  kind: "archive",
21096
21544
  platform,
@@ -21135,13 +21583,13 @@ function buildReleaseAssets(rootDir, config, version, targets) {
21135
21583
  return assets;
21136
21584
  }
21137
21585
  function readNpmPackageName(rootDir, config) {
21138
- const packageDir = resolve22(rootDir, config.outDir, "opencode");
21139
- const packageJsonPath = resolve22(packageDir, "package.json");
21140
- if (!existsSync27(packageJsonPath)) {
21586
+ const packageDir = resolve23(rootDir, config.outDir, "opencode");
21587
+ const packageJsonPath = resolve23(packageDir, "package.json");
21588
+ if (!existsSync25(packageJsonPath)) {
21141
21589
  return {};
21142
21590
  }
21143
21591
  try {
21144
- const pkg = JSON.parse(readFileSync15(packageJsonPath, "utf-8"));
21592
+ const pkg = JSON.parse(readFileSync16(packageJsonPath, "utf-8"));
21145
21593
  return {
21146
21594
  packageName: pkg.name,
21147
21595
  packageDir
@@ -21190,7 +21638,7 @@ function collectChecks(args2) {
21190
21638
  if (args2.npmEnabled) {
21191
21639
  checks.push({
21192
21640
  name: "npm-package-ready",
21193
- ok: Boolean(args2.packageDir && existsSync27(resolve22(args2.packageDir, "package.json")) && args2.packageName),
21641
+ ok: Boolean(args2.packageDir && existsSync25(resolve23(args2.packageDir, "package.json")) && args2.packageName),
21194
21642
  code: "npm-package-ready",
21195
21643
  detail: args2.packageDir ? `OpenCode package dir: ${args2.packageDir}` : "No npm-backed target package found."
21196
21644
  });
@@ -21689,7 +22137,7 @@ claude plugin install "\${PLUGIN_NAME}@\${MARKETPLACE_NAME}" --scope user
21689
22137
 
21690
22138
  echo
21691
22139
  echo "Installed \${PLUGIN_NAME}@\${MARKETPLACE_NAME} into Claude Code user scope."
21692
- echo "If Claude is already open, run /reload-plugins in the active session."
22140
+ echo "${getPublishReloadInstruction("claude-code")}"
21693
22141
  `;
21694
22142
  }
21695
22143
  function renderInstallCursorScript(config) {
@@ -21746,7 +22194,7 @@ ${renderInstallerMcpPathMaterializationSnippet("cursor", "$INSTALL_DIR")}
21746
22194
  ${renderInstallerRuntimeBootstrapSnippet("$INSTALL_DIR")}
21747
22195
 
21748
22196
  echo "Installed $PLUGIN_NAME to $INSTALL_DIR"
21749
- echo "If Cursor is already open, use Developer: Reload Window or restart Cursor so the plugin is picked up."
22197
+ echo "${getPublishReloadInstruction("cursor")}"
21750
22198
  `;
21751
22199
  }
21752
22200
  function renderInstallCodexScript(config) {
@@ -21863,7 +22311,7 @@ NODE
21863
22311
 
21864
22312
  echo "Installed $PLUGIN_NAME to $INSTALL_DIR"
21865
22313
  echo "Updated Codex marketplace catalog at $MARKETPLACE_PATH"
21866
- echo "If Codex is already open, use Plugins > Refresh if that action is available in your current UI, or restart Codex so the plugin is picked up."
22314
+ echo "${getPublishReloadInstruction("codex")}"
21867
22315
  `;
21868
22316
  }
21869
22317
  function renderInstallOpenCodeScript(config) {
@@ -22010,7 +22458,7 @@ fi
22010
22458
  echo "Installed $PLUGIN_NAME plugin code to $INSTALL_DIR"
22011
22459
  echo "Installed OpenCode wrapper at $ENTRY_PATH"
22012
22460
  echo "Synced namespaced skills into $SKILLS_ROOT"
22013
- echo "If OpenCode is already open, restart or reload it so the plugin is picked up."
22461
+ echo "${getPublishReloadInstruction("opencode")}"
22014
22462
  `;
22015
22463
  }
22016
22464
  function replaceInstallerPlaceholders(script, config, context) {
@@ -22035,9 +22483,9 @@ function renderInstallerScript(asset, config, context) {
22035
22483
  throw new Error(`No installer template for asset ${asset.name}`);
22036
22484
  }
22037
22485
  function writeChecksumFile(tempRoot, files) {
22038
- const checksumPath = resolve22(tempRoot, "SHA256SUMS.txt");
22486
+ const checksumPath = resolve23(tempRoot, "SHA256SUMS.txt");
22039
22487
  const lines = files.map((filePath) => {
22040
- const digest = createHash("sha256").update(readFileSync15(filePath)).digest("hex");
22488
+ const digest = createHash("sha256").update(readFileSync16(filePath)).digest("hex");
22041
22489
  const name = filePath.split("/").pop();
22042
22490
  return `${digest} ${name}`;
22043
22491
  }).join("\n");
@@ -22046,7 +22494,7 @@ function writeChecksumFile(tempRoot, files) {
22046
22494
  return checksumPath;
22047
22495
  }
22048
22496
  function createReleaseArtifacts(rootDir, config, plan, runCommand) {
22049
- const tempRoot = mkdtempSync2(resolve22(tmpdir3(), "pluxx-publish-"));
22497
+ const tempRoot = mkdtempSync2(resolve23(tmpdir3(), "pluxx-publish-"));
22050
22498
  const created = [];
22051
22499
  const githubRelease = plan.channels.githubRelease;
22052
22500
  if (!githubRelease.repo || !githubRelease.releaseTag) {
@@ -22066,10 +22514,10 @@ function createReleaseArtifacts(rootDir, config, plan, runCommand) {
22066
22514
  try {
22067
22515
  for (const asset of githubRelease.assets) {
22068
22516
  if (asset.kind !== "archive") continue;
22069
- const archivePath = resolve22(tempRoot, asset.name);
22517
+ const archivePath = resolve23(tempRoot, asset.name);
22070
22518
  const result = runCommand(
22071
22519
  "tar",
22072
- ["-czf", archivePath, "-C", resolve22(rootDir, config.outDir), asset.platform],
22520
+ ["-czf", archivePath, "-C", resolve23(rootDir, config.outDir), asset.platform],
22073
22521
  { cwd: rootDir }
22074
22522
  );
22075
22523
  if (result.status !== 0) {
@@ -22079,21 +22527,21 @@ function createReleaseArtifacts(rootDir, config, plan, runCommand) {
22079
22527
  }
22080
22528
  for (const asset of githubRelease.assets) {
22081
22529
  if (asset.kind !== "installer") continue;
22082
- const installerPath = resolve22(tempRoot, asset.name);
22530
+ const installerPath = resolve23(tempRoot, asset.name);
22083
22531
  writeFileSync6(installerPath, renderInstallerScript(asset, config, context));
22084
22532
  chmodSync(installerPath, 493);
22085
22533
  created.push(installerPath);
22086
22534
  }
22087
22535
  const manifestAsset = githubRelease.assets.find((asset) => asset.kind === "manifest");
22088
22536
  if (manifestAsset) {
22089
- const manifestPath = resolve22(tempRoot, manifestAsset.name);
22537
+ const manifestPath = resolve23(tempRoot, manifestAsset.name);
22090
22538
  writeFileSync6(manifestPath, buildReleaseManifest(config, context));
22091
22539
  created.push(manifestPath);
22092
22540
  }
22093
22541
  const checksumAsset = githubRelease.assets.find((asset) => asset.kind === "checksum");
22094
22542
  if (checksumAsset) {
22095
22543
  const checksumPath = writeChecksumFile(tempRoot, created);
22096
- if (checksumPath !== resolve22(tempRoot, checksumAsset.name)) {
22544
+ if (checksumPath !== resolve23(tempRoot, checksumAsset.name)) {
22097
22545
  throw new Error(`Checksum output path mismatch for ${checksumAsset.name}`);
22098
22546
  }
22099
22547
  created.push(checksumPath);
@@ -22248,18 +22696,18 @@ function printJson(value) {
22248
22696
  }
22249
22697
 
22250
22698
  // src/cli/verify-install.ts
22251
- import { existsSync as existsSync28, lstatSync as lstatSync4, readdirSync as readdirSync12, readFileSync as readFileSync16, readlinkSync, realpathSync, statSync as statSync5 } from "fs";
22252
- import { resolve as resolve23 } from "path";
22699
+ import { existsSync as existsSync26, lstatSync as lstatSync4, readdirSync as readdirSync12, readFileSync as readFileSync17, readlinkSync, realpathSync, statSync as statSync5 } from "fs";
22700
+ import { resolve as resolve24 } from "path";
22253
22701
  function buildCheckFromReport(target, pluginName, report) {
22254
22702
  const consumerPath = resolveInstalledConsumerPath(target, pluginName);
22255
- const staleReason = target.built && existsSync28(consumerPath) ? detectStaleInstall(target, pluginName, consumerPath) : void 0;
22703
+ const staleReason = target.built && existsSync26(consumerPath) ? detectStaleInstall(target, pluginName, consumerPath) : void 0;
22256
22704
  const stale = staleReason !== void 0;
22257
22705
  return {
22258
22706
  platform: target.platform,
22259
22707
  installPath: consumerPath,
22260
22708
  consumerPath,
22261
22709
  built: target.built,
22262
- installed: existsSync28(consumerPath),
22710
+ installed: existsSync26(consumerPath),
22263
22711
  stale,
22264
22712
  ...staleReason ? { staleReason } : {},
22265
22713
  ok: report.errors === 0 && !stale,
@@ -22285,10 +22733,10 @@ function manifestPathForPlatform2(platform) {
22285
22733
  function readInstalledManifestVersion(rootDir, platform) {
22286
22734
  const manifestPath = manifestPathForPlatform2(platform);
22287
22735
  if (!manifestPath) return void 0;
22288
- const filepath = resolve23(rootDir, manifestPath);
22289
- if (!existsSync28(filepath)) return void 0;
22736
+ const filepath = resolve24(rootDir, manifestPath);
22737
+ if (!existsSync26(filepath)) return void 0;
22290
22738
  try {
22291
- const manifest = JSON.parse(readFileSync16(filepath, "utf-8"));
22739
+ const manifest = JSON.parse(readFileSync17(filepath, "utf-8"));
22292
22740
  return typeof manifest.version === "string" ? manifest.version : void 0;
22293
22741
  } catch {
22294
22742
  return void 0;
@@ -22296,14 +22744,14 @@ function readInstalledManifestVersion(rootDir, platform) {
22296
22744
  }
22297
22745
  function findCodexCacheCandidates(pluginName) {
22298
22746
  const home = process.env.HOME ?? "~";
22299
- const cacheRoot = resolve23(home, ".codex/plugins/cache");
22300
- if (!existsSync28(cacheRoot)) return [];
22747
+ const cacheRoot = resolve24(home, ".codex/plugins/cache");
22748
+ if (!existsSync26(cacheRoot)) return [];
22301
22749
  const candidates = [];
22302
22750
  for (const marketplace of readdirSync12(cacheRoot)) {
22303
- const pluginRoot = resolve23(cacheRoot, marketplace, pluginName);
22304
- if (!existsSync28(pluginRoot)) continue;
22751
+ const pluginRoot = resolve24(cacheRoot, marketplace, pluginName);
22752
+ if (!existsSync26(pluginRoot)) continue;
22305
22753
  for (const versionDir of readdirSync12(pluginRoot)) {
22306
- const candidatePath = resolve23(pluginRoot, versionDir);
22754
+ const candidatePath = resolve24(pluginRoot, versionDir);
22307
22755
  try {
22308
22756
  const stats = statSync5(candidatePath);
22309
22757
  if (!stats.isDirectory()) continue;
@@ -22351,7 +22799,7 @@ function detectStaleInstall(target, pluginName, consumerPath) {
22351
22799
  }
22352
22800
  async function verifyInstall(config, options = {}) {
22353
22801
  const rootDir = options.rootDir ?? process.cwd();
22354
- const distDir = resolve23(rootDir, config.outDir);
22802
+ const distDir = resolve24(rootDir, config.outDir);
22355
22803
  const targets = options.targets ?? config.targets;
22356
22804
  const installPlan = planInstallPlugin(distDir, config.name, targets);
22357
22805
  const filteredPlan = options.builtOnly ? installPlan.filter((target) => target.built) : installPlan;
@@ -22397,9 +22845,8 @@ function getVerifyInstallRecoveryActions(check) {
22397
22845
  }
22398
22846
  if (check.stale) {
22399
22847
  actions.push(`rerun pluxx install --target ${check.platform} to replace the stale local install`);
22400
- if (check.platform === "codex") {
22401
- actions.push("in Codex, use Plugins > Refresh if available, or restart Codex so the plugin cache reloads");
22402
- }
22848
+ const staleAction = getVerifyInstallStaleAction(check.platform);
22849
+ if (staleAction) actions.push(staleAction);
22403
22850
  }
22404
22851
  if (check.errors > 0 && actions.length === 0) {
22405
22852
  actions.push(`run pluxx doctor --consumer "${check.consumerPath}" for the detailed host-specific failure`);
@@ -22408,10 +22855,10 @@ function getVerifyInstallRecoveryActions(check) {
22408
22855
  }
22409
22856
 
22410
22857
  // src/cli/behavioral.ts
22411
- import { existsSync as existsSync29, readFileSync as readFileSync17 } from "fs";
22858
+ import { existsSync as existsSync27, readFileSync as readFileSync18 } from "fs";
22412
22859
  import { mkdtemp as mkdtemp2, rm as rm3 } from "fs/promises";
22413
22860
  import { tmpdir as tmpdir4 } from "os";
22414
- import { resolve as resolve24 } from "path";
22861
+ import { resolve as resolve25 } from "path";
22415
22862
  import { spawn as spawn4 } from "child_process";
22416
22863
  var BEHAVIORAL_CONFIG_PATH = ".pluxx/behavioral-smoke.json";
22417
22864
  var CURSOR_RUNNER_BINARIES2 = ["agent", "cursor-agent"];
@@ -22444,13 +22891,13 @@ function loadBehavioralCases(rootDir, targets, promptOverride) {
22444
22891
  )
22445
22892
  }];
22446
22893
  }
22447
- const filePath = resolve24(rootDir, BEHAVIORAL_CONFIG_PATH);
22448
- if (!existsSync29(filePath)) {
22894
+ const filePath = resolve25(rootDir, BEHAVIORAL_CONFIG_PATH);
22895
+ if (!existsSync27(filePath)) {
22449
22896
  throw new Error(
22450
22897
  `No behavioral smoke config found at ${BEHAVIORAL_CONFIG_PATH}. Add that file or pass --behavioral-prompt to define a real example query.`
22451
22898
  );
22452
22899
  }
22453
- const parsed = JSON.parse(readFileSync17(filePath, "utf-8"));
22900
+ const parsed = JSON.parse(readFileSync18(filePath, "utf-8"));
22454
22901
  if (!parsed || typeof parsed !== "object" || !Array.isArray(parsed.cases) || parsed.cases.length === 0) {
22455
22902
  throw new Error(`${BEHAVIORAL_CONFIG_PATH} must contain a non-empty "cases" array.`);
22456
22903
  }
@@ -22541,8 +22988,8 @@ async function executeBehavioralCommand(platform, command2, cwd) {
22541
22988
  let codexLastMessagePath = null;
22542
22989
  const runtimeCommand = [...command2];
22543
22990
  if (platform === "codex") {
22544
- codexOutputDir = await mkdtemp2(resolve24(tmpdir4(), "pluxx-codex-behavioral-"));
22545
- codexLastMessagePath = resolve24(codexOutputDir, "last-message.txt");
22991
+ codexOutputDir = await mkdtemp2(resolve25(tmpdir4(), "pluxx-codex-behavioral-"));
22992
+ codexLastMessagePath = resolve25(codexOutputDir, "last-message.txt");
22546
22993
  runtimeCommand.splice(2, 0, "--output-last-message", codexLastMessagePath);
22547
22994
  }
22548
22995
  try {
@@ -22560,7 +23007,7 @@ async function executeBehavioralCommand(platform, command2, cwd) {
22560
23007
  child.on("close", (code) => {
22561
23008
  const stdout = Buffer.concat(stdoutChunks).toString("utf-8");
22562
23009
  const stderr = Buffer.concat(stderrChunks).toString("utf-8");
22563
- const codexMessage = codexLastMessagePath && existsSync29(codexLastMessagePath) ? readFileSync17(codexLastMessagePath, "utf-8") : "";
23010
+ const codexMessage = codexLastMessagePath && existsSync27(codexLastMessagePath) ? readFileSync18(codexLastMessagePath, "utf-8") : "";
22564
23011
  resolvePromise({
22565
23012
  exitCode: code ?? 1,
22566
23013
  response: codexMessage.trim() || stdout.trim() || stderr.trim()
@@ -22623,9 +23070,9 @@ function shellQuote2(value) {
22623
23070
  }
22624
23071
 
22625
23072
  // src/cli/discover-installed-mcp.ts
22626
- import { existsSync as existsSync30, readFileSync as readFileSync18 } from "fs";
23073
+ import { existsSync as existsSync28, readFileSync as readFileSync19 } from "fs";
22627
23074
  import { homedir as homedir2 } from "os";
22628
- import { resolve as resolve25 } from "path";
23075
+ import { resolve as resolve26 } from "path";
22629
23076
  var INSTALLED_MCP_HOSTS = ["claude-code", "cursor", "codex", "opencode"];
22630
23077
  function discoverInstalledMcpServers(options = {}) {
22631
23078
  const rootDir = options.rootDir ?? process.cwd();
@@ -22634,7 +23081,7 @@ function discoverInstalledMcpServers(options = {}) {
22634
23081
  const results = [];
22635
23082
  const seen = /* @__PURE__ */ new Set();
22636
23083
  for (const candidate of installedMcpFileCandidates(rootDir, homeDir)) {
22637
- if (!hostSet.has(candidate.host) || !existsSync30(candidate.path)) continue;
23084
+ if (!hostSet.has(candidate.host) || !existsSync28(candidate.path)) continue;
22638
23085
  const parsed = candidate.parser === "json" ? parseJsonMcpFile(candidate.path, candidate.host) : parseCodexTomlMcpFile(candidate.path);
22639
23086
  for (const discovered of parsed) {
22640
23087
  const key = `${discovered.host}:${discovered.serverName}:${discovered.sourcePath}`;
@@ -22697,27 +23144,27 @@ function formatInstalledMcpSource(discovered) {
22697
23144
  }
22698
23145
  function installedMcpFileCandidates(rootDir, homeDir) {
22699
23146
  return [
22700
- { host: "claude-code", path: resolve25(rootDir, ".mcp.json"), parser: "json" },
22701
- { host: "claude-code", path: resolve25(rootDir, ".claude-plugin/plugin.json"), parser: "json" },
22702
- { host: "claude-code", path: resolve25(rootDir, ".claude/settings.json"), parser: "json" },
22703
- { host: "claude-code", path: resolve25(rootDir, ".claude/settings.local.json"), parser: "json" },
22704
- { host: "claude-code", path: resolve25(homeDir, ".claude/settings.json"), parser: "json" },
22705
- { host: "claude-code", path: resolve25(homeDir, ".claude/settings.local.json"), parser: "json" },
22706
- { host: "claude-code", path: resolve25(homeDir, ".claude.json"), parser: "json" },
22707
- { host: "cursor", path: resolve25(rootDir, "mcp.json"), parser: "json" },
22708
- { host: "cursor", path: resolve25(rootDir, ".cursor/mcp.json"), parser: "json" },
22709
- { host: "cursor", path: resolve25(homeDir, ".cursor/mcp.json"), parser: "json" },
22710
- { host: "codex", path: resolve25(rootDir, ".mcp.json"), parser: "json" },
22711
- { host: "codex", path: resolve25(rootDir, ".codex/config.toml"), parser: "toml" },
22712
- { host: "codex", path: resolve25(homeDir, ".codex/config.toml"), parser: "toml" },
22713
- { host: "opencode", path: resolve25(rootDir, "opencode.json"), parser: "json" },
22714
- { host: "opencode", path: resolve25(rootDir, ".opencode.json"), parser: "json" },
22715
- { host: "opencode", path: resolve25(homeDir, ".config/opencode/opencode.json"), parser: "json" }
23147
+ { host: "claude-code", path: resolve26(rootDir, ".mcp.json"), parser: "json" },
23148
+ { host: "claude-code", path: resolve26(rootDir, ".claude-plugin/plugin.json"), parser: "json" },
23149
+ { host: "claude-code", path: resolve26(rootDir, ".claude/settings.json"), parser: "json" },
23150
+ { host: "claude-code", path: resolve26(rootDir, ".claude/settings.local.json"), parser: "json" },
23151
+ { host: "claude-code", path: resolve26(homeDir, ".claude/settings.json"), parser: "json" },
23152
+ { host: "claude-code", path: resolve26(homeDir, ".claude/settings.local.json"), parser: "json" },
23153
+ { host: "claude-code", path: resolve26(homeDir, ".claude.json"), parser: "json" },
23154
+ { host: "cursor", path: resolve26(rootDir, "mcp.json"), parser: "json" },
23155
+ { host: "cursor", path: resolve26(rootDir, ".cursor/mcp.json"), parser: "json" },
23156
+ { host: "cursor", path: resolve26(homeDir, ".cursor/mcp.json"), parser: "json" },
23157
+ { host: "codex", path: resolve26(rootDir, ".mcp.json"), parser: "json" },
23158
+ { host: "codex", path: resolve26(rootDir, ".codex/config.toml"), parser: "toml" },
23159
+ { host: "codex", path: resolve26(homeDir, ".codex/config.toml"), parser: "toml" },
23160
+ { host: "opencode", path: resolve26(rootDir, "opencode.json"), parser: "json" },
23161
+ { host: "opencode", path: resolve26(rootDir, ".opencode.json"), parser: "json" },
23162
+ { host: "opencode", path: resolve26(homeDir, ".config/opencode/opencode.json"), parser: "json" }
22716
23163
  ];
22717
23164
  }
22718
23165
  function parseJsonMcpFile(path, host) {
22719
23166
  try {
22720
- const raw = JSON.parse(readFileSync18(path, "utf-8"));
23167
+ const raw = JSON.parse(readFileSync19(path, "utf-8"));
22721
23168
  const servers = extractJsonMcpServers(raw, path, host);
22722
23169
  return Object.entries(servers).flatMap(([serverName, config]) => {
22723
23170
  const normalized = normalizeCommonMcpServer(config, host);
@@ -22752,7 +23199,7 @@ function extractJsonMcpServers(raw, path, host) {
22752
23199
  return {};
22753
23200
  }
22754
23201
  function parseCodexTomlMcpFile(path) {
22755
- const text = readFileSync18(path, "utf-8");
23202
+ const text = readFileSync19(path, "utf-8");
22756
23203
  const servers = {};
22757
23204
  let currentServer;
22758
23205
  let currentSubtable;
@@ -23125,7 +23572,7 @@ function isHelpRequested(input) {
23125
23572
  }
23126
23573
  function getCliPackageVersion() {
23127
23574
  const packageJsonPath = new URL("../../package.json", import.meta.url);
23128
- const raw = JSON.parse(readFileSync19(packageJsonPath, "utf-8"));
23575
+ const raw = JSON.parse(readFileSync20(packageJsonPath, "utf-8"));
23129
23576
  if (typeof raw.version !== "string" || raw.version.trim() === "") {
23130
23577
  throw new Error("Unable to determine the installed pluxx version from package.json.");
23131
23578
  }
@@ -23303,7 +23750,7 @@ async function maybeInstallBuiltOutputs(config, platforms, options = {}) {
23303
23750
  return {
23304
23751
  enabled: true,
23305
23752
  platforms,
23306
- notes: getInstallFollowupNotes(platforms),
23753
+ notes: getInstallFollowupNotes2(platforms),
23307
23754
  installTargets: installPlan.map((target) => ({
23308
23755
  platform: target.platform,
23309
23756
  pluginDir: target.description,
@@ -23796,7 +24243,7 @@ async function planInitContextArtifactFiles(rootDir, contextPack) {
23796
24243
  return plannedFiles;
23797
24244
  }
23798
24245
  async function planAuxiliaryFile(rootDir, relativePath, content) {
23799
- const filePath = resolve26(rootDir, relativePath);
24246
+ const filePath = resolve27(rootDir, relativePath);
23800
24247
  const action = await planTextFileAction(filePath, content);
23801
24248
  return {
23802
24249
  relativePath,
@@ -23981,7 +24428,7 @@ ${mcpBlock}${brandBlock}
23981
24428
  targets: [${targetsList}],
23982
24429
  })
23983
24430
  `;
23984
- await writeTextFile(resolve26(process.cwd(), "pluxx.config.ts"), template);
24431
+ await writeTextFile(resolve27(process.cwd(), "pluxx.config.ts"), template);
23985
24432
  const skillDir = `skills/${skillName}`;
23986
24433
  await mkdir4(skillDir, { recursive: true });
23987
24434
  const skillContent = `---
@@ -24003,7 +24450,7 @@ Describe how agents should use this skill.
24003
24450
  Example prompt or command here
24004
24451
  \`\`\`
24005
24452
  `;
24006
- await writeTextFile(resolve26(process.cwd(), `${skillDir}/SKILL.md`), skillContent);
24453
+ await writeTextFile(resolve27(process.cwd(), `${skillDir}/SKILL.md`), skillContent);
24007
24454
  console.log("");
24008
24455
  console.log(" Created:");
24009
24456
  console.log(" pluxx.config.ts");
@@ -24249,7 +24696,7 @@ ${formatAuthRequiredMessage("init", retryError, source)}`);
24249
24696
  if (!runtime.dryRun) {
24250
24697
  await applyMcpScaffoldPlan(process.cwd(), plan);
24251
24698
  for (const file of contextArtifactFiles) {
24252
- await writeTextFile(resolve26(process.cwd(), file.relativePath), file.content);
24699
+ await writeTextFile(resolve27(process.cwd(), file.relativePath), file.content);
24253
24700
  }
24254
24701
  }
24255
24702
  const lintResult = runtime.dryRun ? { errors: 0, warnings: 0, issues: [] } : await lintProject(process.cwd());
@@ -24417,7 +24864,7 @@ async function runSync() {
24417
24864
  async function runDoctor() {
24418
24865
  const consumerMode = readFlag(args, "--consumer");
24419
24866
  const doctorPath = args.slice(1).find((value) => !value.startsWith("-"));
24420
- const rootDir = doctorPath ? resolve26(process.cwd(), doctorPath) : process.cwd();
24867
+ const rootDir = doctorPath ? resolve27(process.cwd(), doctorPath) : process.cwd();
24421
24868
  const report = consumerMode ? await doctorConsumer(rootDir) : await doctorProject(rootDir);
24422
24869
  if (runtime.jsonOutput) {
24423
24870
  printJson(report);
@@ -24918,7 +25365,7 @@ ${formatAuthRequiredMessage("autopilot", retryError, source)}`);
24918
25365
  install: installRequested ? {
24919
25366
  enabled: true,
24920
25367
  platforms: installTargets,
24921
- notes: getInstallFollowupNotes(installTargets),
25368
+ notes: getInstallFollowupNotes2(installTargets),
24922
25369
  installTargets: []
24923
25370
  } : void 0,
24924
25371
  steps: totalSteps,
@@ -25312,7 +25759,7 @@ async function runInstall() {
25312
25759
  dryRun: true,
25313
25760
  pluginName: config.name,
25314
25761
  platforms,
25315
- notes: getInstallFollowupNotes(platforms),
25762
+ notes: getInstallFollowupNotes2(platforms),
25316
25763
  trustRequired: hookCommands.length > 0,
25317
25764
  userConfig: plannedUserConfig.map((entry) => ({
25318
25765
  key: entry.field.key,
@@ -25346,7 +25793,7 @@ async function runInstall() {
25346
25793
  if (listHookCommands(config.hooks).length > 0) {
25347
25794
  console.log(" trust reminder: this plugin defines local hook commands; install requires review or --trust");
25348
25795
  }
25349
- for (const note of getInstallFollowupNotes(platforms)) {
25796
+ for (const note of getInstallFollowupNotes2(platforms)) {
25350
25797
  console.log(` note: ${note}`);
25351
25798
  }
25352
25799
  }
@@ -25425,7 +25872,7 @@ async function runVerifyInstall() {
25425
25872
  const targets = parseTargetFlagValues(args);
25426
25873
  const config = await loadConfig();
25427
25874
  if (runtime.dryRun) {
25428
- const distDir = resolve26(process.cwd(), config.outDir);
25875
+ const distDir = resolve27(process.cwd(), config.outDir);
25429
25876
  const plan = planInstallPlugin(distDir, config.name, targets ?? config.targets);
25430
25877
  const summary = {
25431
25878
  dryRun: true,