@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/agents.d.ts +22 -0
- package/dist/agents.d.ts.map +1 -1
- package/dist/cli/index.js +1143 -696
- package/dist/cli/init-from-mcp.d.ts.map +1 -1
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/lint.d.ts.map +1 -1
- package/dist/cli/migrate.d.ts.map +1 -1
- package/dist/cli/publish.d.ts.map +1 -1
- package/dist/cli/verify-install.d.ts.map +1 -1
- package/dist/commands.d.ts +11 -0
- package/dist/commands.d.ts.map +1 -1
- package/dist/compiler-intent.d.ts +8 -0
- package/dist/compiler-intent.d.ts.map +1 -1
- package/dist/distribution-lifecycle.d.ts +6 -0
- package/dist/distribution-lifecycle.d.ts.map +1 -0
- package/dist/generators/claude-code/index.d.ts.map +1 -1
- package/dist/generators/codex/index.d.ts.map +1 -1
- package/dist/generators/cursor/index.d.ts.map +1 -1
- package/dist/generators/opencode/index.d.ts.map +1 -1
- package/dist/generators/shared/claude-family.d.ts.map +1 -1
- package/dist/hook-command-env.d.ts +2 -0
- package/dist/hook-command-env.d.ts.map +1 -0
- package/dist/hook-translation-registry.d.ts.map +1 -1
- package/dist/index.js +84 -28
- package/dist/instructions.d.ts +6 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/schema.d.ts +10073 -1623
- package/dist/schema.d.ts.map +1 -1
- package/dist/skills.d.ts +12 -0
- package/dist/skills.d.ts.map +1 -1
- package/dist/validation/platform-rules.d.ts.map +1 -1
- package/package.json +1 -1
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
|
|
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 === "
|
|
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
|
|
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 {
|
|
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: {
|
|
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
|
|
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
|
|
5799
|
-
import { basename, resolve as
|
|
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 =
|
|
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 =
|
|
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 || !
|
|
5981
|
+
if (!agentsDir || !existsSync6(agentsDir)) return [];
|
|
5906
5982
|
return walkMarkdownFiles(agentsDir).sort((a, b) => a.localeCompare(b)).map(parseCanonicalAgentFile);
|
|
5907
5983
|
}
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5920
|
-
|
|
5921
|
-
|
|
5922
|
-
|
|
5923
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
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
|
-
...
|
|
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
|
-
|
|
6144
|
-
|
|
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
|
|
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 =
|
|
6168
|
-
const bash =
|
|
6169
|
-
const 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
|
|
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
|
|
6206
|
-
import { relative as relative2, resolve as
|
|
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 =
|
|
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(
|
|
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 =
|
|
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 =
|
|
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(
|
|
6410
|
-
`description: ${JSON.stringify(
|
|
6629
|
+
`name: ${JSON.stringify(metadata.name)}`,
|
|
6630
|
+
`description: ${JSON.stringify(metadata.description)}`
|
|
6411
6631
|
];
|
|
6412
|
-
if (
|
|
6413
|
-
frontmatter.push(`model: ${JSON.stringify(
|
|
6632
|
+
if (metadata.model) {
|
|
6633
|
+
frontmatter.push(`model: ${JSON.stringify(metadata.model)}`);
|
|
6414
6634
|
}
|
|
6415
|
-
|
|
6416
|
-
|
|
6417
|
-
frontmatter.push(`effort: ${JSON.stringify(effort)}`);
|
|
6635
|
+
if (metadata.modelReasoningEffort) {
|
|
6636
|
+
frontmatter.push(`effort: ${JSON.stringify(metadata.modelReasoningEffort)}`);
|
|
6418
6637
|
}
|
|
6419
|
-
|
|
6420
|
-
|
|
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 (
|
|
6432
|
-
frontmatter.push(`skills: ${
|
|
6649
|
+
if (metadata.skills) {
|
|
6650
|
+
frontmatter.push(`skills: ${metadata.skills}`);
|
|
6433
6651
|
}
|
|
6434
|
-
if (
|
|
6435
|
-
frontmatter.push(`memory: ${JSON.stringify(
|
|
6652
|
+
if (metadata.memory) {
|
|
6653
|
+
frontmatter.push(`memory: ${JSON.stringify(metadata.memory)}`);
|
|
6436
6654
|
}
|
|
6437
|
-
if (typeof
|
|
6438
|
-
frontmatter.push(`background: ${
|
|
6655
|
+
if (typeof metadata.background === "boolean") {
|
|
6656
|
+
frontmatter.push(`background: ${metadata.background}`);
|
|
6439
6657
|
}
|
|
6440
|
-
if (
|
|
6441
|
-
frontmatter.push(`isolation: ${JSON.stringify(
|
|
6658
|
+
if (metadata.isolation) {
|
|
6659
|
+
frontmatter.push(`isolation: ${JSON.stringify(metadata.isolation)}`);
|
|
6442
6660
|
}
|
|
6443
|
-
if (
|
|
6444
|
-
frontmatter.push(`color: ${JSON.stringify(
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
6556
|
-
const bash =
|
|
6557
|
-
const legacyTools =
|
|
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
|
|
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
|
-
...
|
|
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
|
-
|
|
6745
|
-
|
|
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(
|
|
6759
|
-
`description: ${JSON.stringify(
|
|
6986
|
+
`name: ${JSON.stringify(metadata.name)}`,
|
|
6987
|
+
`description: ${JSON.stringify(metadata.description)}`
|
|
6760
6988
|
];
|
|
6761
|
-
if (
|
|
6762
|
-
frontmatter.push(`model: ${JSON.stringify(
|
|
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
|
|
6792
|
-
import { relative as relative3, resolve as
|
|
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 =
|
|
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 || !
|
|
7104
|
+
if (!commandsDir || !existsSync9(commandsDir)) return [];
|
|
6878
7105
|
return walkMarkdownFiles2(commandsDir).sort((a, b) => a.localeCompare(b)).map((filePath) => {
|
|
6879
|
-
const content =
|
|
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
|
-
|
|
7022
|
-
|
|
7023
|
-
|
|
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(
|
|
7043
|
-
`description = ${JSON.stringify(
|
|
7283
|
+
`name = ${JSON.stringify(metadata.name)}`,
|
|
7284
|
+
`description = ${JSON.stringify(metadata.description)}`
|
|
7044
7285
|
];
|
|
7045
|
-
if (
|
|
7046
|
-
lines.push(`model = ${JSON.stringify(
|
|
7286
|
+
if (metadata.model) {
|
|
7287
|
+
lines.push(`model = ${JSON.stringify(metadata.model)}`);
|
|
7047
7288
|
}
|
|
7048
|
-
if (
|
|
7049
|
-
lines.push(`model_reasoning_effort = ${JSON.stringify(
|
|
7289
|
+
if (metadata.modelReasoningEffort) {
|
|
7290
|
+
lines.push(`model_reasoning_effort = ${JSON.stringify(metadata.modelReasoningEffort)}`);
|
|
7050
7291
|
}
|
|
7051
|
-
if (
|
|
7052
|
-
lines.push(`sandbox_mode = ${JSON.stringify(
|
|
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
|
-
|
|
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 =
|
|
7094
|
-
|
|
7095
|
-
|
|
7096
|
-
|
|
7097
|
-
|
|
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:
|
|
7372
|
+
enforcedByPluginBundle: true,
|
|
7117
7373
|
featureFlag: "codex_hooks",
|
|
7118
|
-
note: "Codex hook configuration
|
|
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
|
-
|
|
7157
|
-
|
|
7158
|
-
|
|
7159
|
-
|
|
7160
|
-
|
|
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) =>
|
|
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
|
|
7182
|
-
import { basename as basename3, resolve as
|
|
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
|
|
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
|
-
|
|
7535
|
-
|
|
7536
|
-
|
|
7537
|
-
...
|
|
7538
|
-
...
|
|
7539
|
-
...
|
|
7540
|
-
...
|
|
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:
|
|
7829
|
+
description: metadata.description
|
|
7553
7830
|
};
|
|
7554
|
-
if (
|
|
7555
|
-
definition.prompt =
|
|
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 (
|
|
7561
|
-
definition.
|
|
7834
|
+
if (metadata.mode) {
|
|
7835
|
+
definition.mode = metadata.mode;
|
|
7562
7836
|
}
|
|
7563
|
-
if (
|
|
7564
|
-
definition.
|
|
7837
|
+
if (metadata.model) {
|
|
7838
|
+
definition.model = metadata.model;
|
|
7565
7839
|
}
|
|
7566
|
-
if (typeof
|
|
7567
|
-
definition.
|
|
7840
|
+
if (typeof metadata.temperature === "number") {
|
|
7841
|
+
definition.temperature = metadata.temperature;
|
|
7568
7842
|
}
|
|
7569
|
-
if (typeof
|
|
7570
|
-
definition.steps =
|
|
7843
|
+
if (typeof metadata.steps === "number") {
|
|
7844
|
+
definition.steps = metadata.steps;
|
|
7571
7845
|
}
|
|
7572
|
-
if (typeof
|
|
7573
|
-
definition.disable =
|
|
7846
|
+
if (typeof metadata.disabled === "boolean") {
|
|
7847
|
+
definition.disable = metadata.disabled;
|
|
7574
7848
|
}
|
|
7575
|
-
if (
|
|
7576
|
-
definition.hidden =
|
|
7849
|
+
if (metadata.hidden) {
|
|
7850
|
+
definition.hidden = metadata.hidden;
|
|
7577
7851
|
}
|
|
7578
|
-
if (
|
|
7579
|
-
definition.color =
|
|
7852
|
+
if (metadata.color) {
|
|
7853
|
+
definition.color = metadata.color;
|
|
7580
7854
|
}
|
|
7581
|
-
if (typeof
|
|
7582
|
-
definition.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[
|
|
7870
|
+
output[metadata.name] = definition;
|
|
7600
7871
|
}
|
|
7601
7872
|
return output;
|
|
7602
7873
|
}
|
|
7603
7874
|
getInstructionsContent() {
|
|
7604
|
-
|
|
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 =
|
|
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 =
|
|
7680
|
-
if (!
|
|
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 =
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
8344
|
+
const outDir = resolve11(rootDir, config.outDir);
|
|
8077
8345
|
const rel = relative6(rootDir, outDir);
|
|
8078
|
-
if (rel.startsWith("..") ||
|
|
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
|
|
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
|
|
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
|
|
8107
|
-
import { resolve as
|
|
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
|
|
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", "
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
9589
|
-
if (!
|
|
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 =
|
|
9605
|
-
if (!
|
|
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
|
|
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 =
|
|
9724
|
-
if (!
|
|
9725
|
-
const content =
|
|
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 =
|
|
9746
|
-
if (!
|
|
9747
|
-
const content =
|
|
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 =
|
|
9767
|
-
if (
|
|
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 (!
|
|
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 =
|
|
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(
|
|
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 =
|
|
9952
|
-
if (!
|
|
10304
|
+
const settingsPath = resolve12(dir, "settings.json");
|
|
10305
|
+
if (!existsSync16(settingsPath)) return;
|
|
9953
10306
|
try {
|
|
9954
|
-
const content = JSON.parse(
|
|
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
|
-
|
|
10372
|
+
if (hasPluxxCodexHooksFlag) return;
|
|
10020
10373
|
pushIssue(issues, {
|
|
10021
10374
|
level: "warning",
|
|
10022
10375
|
code: "codex-hooks-external-config",
|
|
10023
|
-
message:
|
|
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
|
-
|
|
10115
|
-
|
|
10116
|
-
|
|
10117
|
-
|
|
10118
|
-
|
|
10119
|
-
|
|
10120
|
-
|
|
10121
|
-
|
|
10122
|
-
|
|
10123
|
-
|
|
10124
|
-
|
|
10125
|
-
|
|
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 =
|
|
10397
|
-
const agentFiles =
|
|
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 =
|
|
10822
|
+
const skillsDir = resolve12(dir, lintConfig.skills);
|
|
10418
10823
|
let skillFiles = [];
|
|
10419
|
-
if (!
|
|
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,
|
|
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
|
|
10490
|
-
import { resolve as
|
|
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
|
|
10494
|
-
import { resolve as
|
|
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
|
|
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
|
|
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 =
|
|
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(
|
|
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 =
|
|
10839
|
-
const skillRoot =
|
|
10840
|
-
const commandsRoot =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
11352
|
-
if (!
|
|
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 =
|
|
12407
|
-
if (!
|
|
12821
|
+
const metadataPath = resolve14(rootDir, MCP_SCAFFOLD_METADATA_PATH);
|
|
12822
|
+
if (!existsSync18(metadataPath)) {
|
|
12408
12823
|
return null;
|
|
12409
12824
|
}
|
|
12410
|
-
const parsed = JSON.parse(
|
|
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 =
|
|
12438
|
-
if (!
|
|
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 =
|
|
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 =
|
|
12484
|
-
if (!
|
|
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 =
|
|
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 =
|
|
12548
|
-
if (!
|
|
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 =
|
|
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 =
|
|
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
|
|
12885
|
-
import { dirname as dirname5, isAbsolute, relative as relative9, resolve as
|
|
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((
|
|
13582
|
+
const endpointReady = new Promise((resolve28, reject) => {
|
|
13168
13583
|
resolveEndpoint = (value) => {
|
|
13169
13584
|
endpointSettled = true;
|
|
13170
|
-
|
|
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((
|
|
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
|
-
|
|
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((
|
|
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
|
-
|
|
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 (!
|
|
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(
|
|
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(
|
|
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 (!
|
|
13690
|
-
const oldContent =
|
|
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 (!
|
|
13697
|
-
const currentContent =
|
|
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 (!
|
|
13740
|
-
const after =
|
|
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(
|
|
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(
|
|
13815
|
-
const projectDir =
|
|
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 (
|
|
13829
|
-
return JSON.parse(
|
|
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 (!
|
|
13842
|
-
const oldContent =
|
|
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 (!
|
|
13847
|
-
const currentContent =
|
|
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 (!
|
|
13857
|
-
contents.set(file,
|
|
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 (!
|
|
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 (!
|
|
13871
|
-
return hasMeaningfulCustomContent(
|
|
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 =
|
|
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 =
|
|
14043
|
-
const filepath =
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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 =
|
|
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(
|
|
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 =
|
|
14155
|
-
if (!options.allowMissingContext && !
|
|
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) =>
|
|
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 =
|
|
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(
|
|
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 =
|
|
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(
|
|
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 =
|
|
14335
|
-
if (!
|
|
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 =
|
|
14349
|
-
if (
|
|
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 ?
|
|
14384
|
-
const commandsDir = config.commands ?
|
|
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 =
|
|
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 =
|
|
14719
|
-
if (!
|
|
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() ||
|
|
16008
|
-
return await readTomlStringValue(
|
|
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() ||
|
|
16012
|
-
const configPath =
|
|
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
|
-
|
|
16031
|
-
|
|
16032
|
-
|
|
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(
|
|
16117
|
-
codexLastMessagePath =
|
|
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 ?
|
|
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(
|
|
16237
|
-
const shimPath =
|
|
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() ||
|
|
16259
|
-
const isolatedCodexHome = await mkdtemp(
|
|
16260
|
-
await mkdir3(
|
|
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 =
|
|
16263
|
-
if (!
|
|
16264
|
-
await copyFile(sourcePath,
|
|
16677
|
+
const sourcePath = resolve17(currentCodexHome, relativePath);
|
|
16678
|
+
if (!existsSync21(sourcePath)) continue;
|
|
16679
|
+
await copyFile(sourcePath, resolve17(isolatedCodexHome, relativePath));
|
|
16265
16680
|
}
|
|
16266
|
-
const rulesSourceDir =
|
|
16267
|
-
if (
|
|
16268
|
-
const rulesTargetDir =
|
|
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 =
|
|
16271
|
-
if (
|
|
16272
|
-
await copyFile(defaultRulesPath,
|
|
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 =
|
|
16309
|
-
if (!
|
|
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
|
|
16413
|
-
import { basename as basename6, dirname as dirname7, resolve as
|
|
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
|
|
16417
|
-
import { existsSync as
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
17042
|
+
pluginDir: resolve18(home, ".openhands/plugins", pluginName),
|
|
16597
17043
|
description: `~/.openhands/plugins/${pluginName}`
|
|
16598
17044
|
},
|
|
16599
17045
|
{
|
|
16600
17046
|
platform: "warp",
|
|
16601
|
-
pluginDir:
|
|
17047
|
+
pluginDir: resolve18(home, ".warp/plugins", pluginName),
|
|
16602
17048
|
description: `~/.warp/plugins/${pluginName}`
|
|
16603
17049
|
},
|
|
16604
17050
|
{
|
|
16605
17051
|
platform: "gemini-cli",
|
|
16606
|
-
pluginDir:
|
|
17052
|
+
pluginDir: resolve18(home, ".gemini/extensions", pluginName),
|
|
16607
17053
|
description: `~/.gemini/extensions/${pluginName}`
|
|
16608
17054
|
},
|
|
16609
17055
|
{
|
|
16610
17056
|
platform: "roo-code",
|
|
16611
|
-
pluginDir:
|
|
17057
|
+
pluginDir: resolve18(home, ".roo/plugins", pluginName),
|
|
16612
17058
|
description: `~/.roo/plugins/${pluginName}`
|
|
16613
17059
|
},
|
|
16614
17060
|
{
|
|
16615
17061
|
platform: "cline",
|
|
16616
|
-
pluginDir:
|
|
17062
|
+
pluginDir: resolve18(home, ".cline/plugins", pluginName),
|
|
16617
17063
|
description: `~/.cline/plugins/${pluginName}`
|
|
16618
17064
|
},
|
|
16619
17065
|
{
|
|
16620
17066
|
platform: "amp",
|
|
16621
|
-
pluginDir:
|
|
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
|
|
17108
|
+
return resolve18(home, ".config/opencode/skills");
|
|
16663
17109
|
}
|
|
16664
17110
|
function getOpenCodeInstalledSkillDir(pluginName, skillName) {
|
|
16665
|
-
return
|
|
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 =
|
|
16690
|
-
if (!
|
|
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 =
|
|
16695
|
-
if (!
|
|
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 =
|
|
17145
|
+
const skillPath = resolve18(installedSkillDir, "SKILL.md");
|
|
16700
17146
|
writeFileSync4(
|
|
16701
17147
|
skillPath,
|
|
16702
|
-
namespaceOpenCodeSkill(
|
|
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 (!
|
|
17154
|
+
if (!existsSync22(entryPath)) {
|
|
16709
17155
|
throw new Error(`OpenCode install is incomplete: missing host entry file at ${entryPath}`);
|
|
16710
17156
|
}
|
|
16711
|
-
const entryContent =
|
|
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 =
|
|
16721
|
-
if (!
|
|
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 =
|
|
16725
|
-
if (!
|
|
16726
|
-
const installedSkillPath =
|
|
16727
|
-
if (!
|
|
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 =
|
|
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 (!
|
|
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(
|
|
17188
|
+
rmSync3(resolve18(root, entry.name), { recursive: true, force: true });
|
|
16743
17189
|
removed = true;
|
|
16744
17190
|
}
|
|
16745
17191
|
return removed;
|
|
16746
17192
|
}
|
|
16747
|
-
function
|
|
16748
|
-
|
|
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 =
|
|
17205
|
+
const parentDir = resolve18(target.pluginDir, "..");
|
|
16773
17206
|
mkdirSync4(parentDir, { recursive: true });
|
|
16774
|
-
if (
|
|
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
|
|
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 (!
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
17290
|
+
const parentDir = resolve18(target.pluginDir, "..");
|
|
16858
17291
|
mkdirSync4(parentDir, { recursive: true });
|
|
16859
|
-
if (
|
|
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 =
|
|
16884
|
-
if (!
|
|
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 =
|
|
16916
|
-
if (!
|
|
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 =
|
|
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 =
|
|
16956
|
-
if (!
|
|
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
|
|
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 =
|
|
16990
|
-
if (!
|
|
17422
|
+
const filepath = resolve18(rootDir, manifestPath);
|
|
17423
|
+
if (!existsSync22(filepath)) return void 0;
|
|
16991
17424
|
try {
|
|
16992
|
-
const manifest = JSON.parse(
|
|
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
|
|
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 (
|
|
17017
|
-
if (
|
|
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
|
|
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
|
|
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 =
|
|
17099
|
-
if (!
|
|
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(
|
|
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 && !
|
|
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 || !
|
|
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(
|
|
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 && !
|
|
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 || !
|
|
17598
|
+
if (!mcpPath || !existsSync22(mcpPath)) return [];
|
|
17166
17599
|
try {
|
|
17167
|
-
const parsed = JSON.parse(
|
|
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 || !
|
|
17180
|
-
const content =
|
|
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 =
|
|
17214
|
-
const marketplacePluginDir =
|
|
17215
|
-
const pluginManifestPath =
|
|
17216
|
-
const pluginManifest = JSON.parse(
|
|
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(
|
|
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
|
-
|
|
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 (
|
|
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 =
|
|
17723
|
+
const hadMarketplaceRoot = existsSync22(marketplaceRoot);
|
|
17291
17724
|
rmSync3(marketplaceRoot, { recursive: true, force: true });
|
|
17292
|
-
const hadLegacyPluginDir =
|
|
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 =
|
|
17735
|
+
const sourceDir = resolve18(distDir, target.platform);
|
|
17303
17736
|
return {
|
|
17304
17737
|
...target,
|
|
17305
17738
|
sourceDir,
|
|
17306
|
-
built:
|
|
17307
|
-
existing:
|
|
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 &&
|
|
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
|
|
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 (
|
|
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 (
|
|
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 =
|
|
17512
|
-
if (!
|
|
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 =
|
|
17923
|
-
if (!
|
|
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(
|
|
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 (
|
|
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 (
|
|
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 (
|
|
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 =
|
|
18013
|
-
const indexPath =
|
|
18014
|
-
if (
|
|
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(
|
|
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) =>
|
|
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) =>
|
|
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(
|
|
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 =
|
|
18093
|
-
if (!
|
|
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(
|
|
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 =
|
|
18144
|
-
if (!
|
|
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 =
|
|
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) =>
|
|
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 =
|
|
18207
|
-
if (!
|
|
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 =
|
|
18379
|
-
if (!
|
|
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 (!
|
|
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 =
|
|
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 =
|
|
18530
|
-
if (!
|
|
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 =
|
|
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 =
|
|
18548
|
-
if (!
|
|
18980
|
+
const sourceSkillPath = resolve19(sourceSkillsDir, entry.name, "SKILL.md");
|
|
18981
|
+
if (!existsSync23(sourceSkillPath)) continue;
|
|
18549
18982
|
expectedSkillCount++;
|
|
18550
|
-
const installedSkillPath =
|
|
18551
|
-
if (!
|
|
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 =
|
|
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) =>
|
|
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
|
|
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,
|
|
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
|
|
18833
|
-
import { existsSync as
|
|
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 =
|
|
18842
|
-
if (
|
|
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 =
|
|
18847
|
-
if (
|
|
19279
|
+
const pkgPath = resolve21(pluginDir, "package.json");
|
|
19280
|
+
if (existsSync24(pkgPath)) {
|
|
18848
19281
|
try {
|
|
18849
|
-
const pkg = JSON.parse(
|
|
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(
|
|
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
|
-
|
|
18890
|
-
|
|
19322
|
+
resolve21(pluginDir, ".mcp.json"),
|
|
19323
|
+
resolve21(pluginDir, "mcp.json")
|
|
18891
19324
|
];
|
|
18892
19325
|
try {
|
|
18893
|
-
const manifest = JSON.parse(
|
|
19326
|
+
const manifest = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
|
|
18894
19327
|
if (manifest.mcpServers && typeof manifest.mcpServers === "string") {
|
|
18895
|
-
mcpPaths.unshift(
|
|
19328
|
+
mcpPaths.unshift(resolve21(pluginDir, manifest.mcpServers));
|
|
18896
19329
|
}
|
|
18897
19330
|
} catch {
|
|
18898
19331
|
}
|
|
18899
19332
|
for (const mcpPath of mcpPaths) {
|
|
18900
|
-
if (!
|
|
19333
|
+
if (!existsSync24(mcpPath)) continue;
|
|
18901
19334
|
try {
|
|
18902
|
-
const raw = JSON.parse(
|
|
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
|
-
|
|
18983
|
-
|
|
18984
|
-
|
|
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(
|
|
19420
|
+
const manifest = JSON.parse(readFileSync14(detection.manifestPath, "utf-8"));
|
|
18988
19421
|
if (manifest.hooks && typeof manifest.hooks === "string") {
|
|
18989
|
-
hooksPaths.unshift(
|
|
19422
|
+
hooksPaths.unshift(resolve21(pluginDir, manifest.hooks));
|
|
18990
19423
|
}
|
|
18991
19424
|
} catch {
|
|
18992
19425
|
}
|
|
18993
19426
|
for (const hooksPath of hooksPaths) {
|
|
18994
|
-
if (!
|
|
19427
|
+
if (!existsSync24(hooksPath)) continue;
|
|
18995
19428
|
try {
|
|
18996
|
-
const raw = JSON.parse(
|
|
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 =
|
|
19043
|
-
if (
|
|
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 (!
|
|
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 =
|
|
19084
|
-
if (
|
|
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 =
|
|
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 =
|
|
19128
|
-
if (!
|
|
19129
|
-
const skill = parseSkillMarkdown(
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
19198
|
-
const skillsDir =
|
|
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 =
|
|
19203
|
-
if (!
|
|
19204
|
-
const parsed = parseSkillMarkdown(
|
|
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 =
|
|
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 =
|
|
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 (!
|
|
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 =
|
|
19366
|
-
const parsed = parseCodexAgentToml(
|
|
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(
|
|
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 =
|
|
19439
|
-
if (!
|
|
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 =
|
|
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 =
|
|
19505
|
-
const dest =
|
|
19506
|
-
if (
|
|
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 =
|
|
19522
|
-
const dest =
|
|
19523
|
-
if (
|
|
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 =
|
|
20115
|
+
const pluginDir = resolve21(inputPath);
|
|
19668
20116
|
const outputDir = process.cwd();
|
|
19669
|
-
if (!
|
|
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 =
|
|
19736
|
-
if (
|
|
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 =
|
|
19752
|
-
const destInstr =
|
|
19753
|
-
if (!
|
|
19754
|
-
const content =
|
|
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 =
|
|
19760
|
-
const metadataPath =
|
|
19761
|
-
mkdirSync5(
|
|
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
|
-
|
|
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
|
|
19789
|
-
import { dirname as dirname8, resolve as
|
|
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 =
|
|
19858
|
-
const tape = JSON.parse(
|
|
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 =
|
|
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((
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
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) =>
|
|
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 =
|
|
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 =
|
|
21139
|
-
const packageJsonPath =
|
|
21140
|
-
if (!
|
|
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(
|
|
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 &&
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 "
|
|
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 =
|
|
22486
|
+
const checksumPath = resolve23(tempRoot, "SHA256SUMS.txt");
|
|
22039
22487
|
const lines = files.map((filePath) => {
|
|
22040
|
-
const digest = createHash("sha256").update(
|
|
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(
|
|
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 =
|
|
22517
|
+
const archivePath = resolve23(tempRoot, asset.name);
|
|
22070
22518
|
const result = runCommand(
|
|
22071
22519
|
"tar",
|
|
22072
|
-
["-czf", archivePath, "-C",
|
|
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 =
|
|
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 =
|
|
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 !==
|
|
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
|
|
22252
|
-
import { resolve as
|
|
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 &&
|
|
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:
|
|
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 =
|
|
22289
|
-
if (!
|
|
22736
|
+
const filepath = resolve24(rootDir, manifestPath);
|
|
22737
|
+
if (!existsSync26(filepath)) return void 0;
|
|
22290
22738
|
try {
|
|
22291
|
-
const manifest = JSON.parse(
|
|
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 =
|
|
22300
|
-
if (!
|
|
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 =
|
|
22304
|
-
if (!
|
|
22751
|
+
const pluginRoot = resolve24(cacheRoot, marketplace, pluginName);
|
|
22752
|
+
if (!existsSync26(pluginRoot)) continue;
|
|
22305
22753
|
for (const versionDir of readdirSync12(pluginRoot)) {
|
|
22306
|
-
const candidatePath =
|
|
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 =
|
|
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
|
-
|
|
22401
|
-
|
|
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
|
|
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
|
|
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 =
|
|
22448
|
-
if (!
|
|
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(
|
|
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(
|
|
22545
|
-
codexLastMessagePath =
|
|
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 &&
|
|
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
|
|
23073
|
+
import { existsSync as existsSync28, readFileSync as readFileSync19 } from "fs";
|
|
22627
23074
|
import { homedir as homedir2 } from "os";
|
|
22628
|
-
import { resolve as
|
|
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) || !
|
|
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:
|
|
22701
|
-
{ host: "claude-code", path:
|
|
22702
|
-
{ host: "claude-code", path:
|
|
22703
|
-
{ host: "claude-code", path:
|
|
22704
|
-
{ host: "claude-code", path:
|
|
22705
|
-
{ host: "claude-code", path:
|
|
22706
|
-
{ host: "claude-code", path:
|
|
22707
|
-
{ host: "cursor", path:
|
|
22708
|
-
{ host: "cursor", path:
|
|
22709
|
-
{ host: "cursor", path:
|
|
22710
|
-
{ host: "codex", path:
|
|
22711
|
-
{ host: "codex", path:
|
|
22712
|
-
{ host: "codex", path:
|
|
22713
|
-
{ host: "opencode", path:
|
|
22714
|
-
{ host: "opencode", path:
|
|
22715
|
-
{ host: "opencode", path:
|
|
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(
|
|
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 =
|
|
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(
|
|
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:
|
|
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 =
|
|
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(
|
|
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(
|
|
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(
|
|
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 ?
|
|
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:
|
|
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:
|
|
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
|
|
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 =
|
|
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,
|