@orchid-labs/pluxx 0.1.7 → 0.1.8

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/index.js CHANGED
@@ -8670,6 +8670,110 @@ import { createHash } from "crypto";
8670
8670
  import { resolve as resolve2 } from "path";
8671
8671
  import { spawnSync } from "child_process";
8672
8672
  import { tmpdir } from "os";
8673
+
8674
+ // src/user-config.ts
8675
+ var ENV_VAR_NAME = /^[A-Za-z_][A-Za-z0-9_]*$/;
8676
+ var PLACEHOLDER_SECRET_PATTERNS = [
8677
+ /\bdummy\b/i,
8678
+ /\bplaceholder\b/i,
8679
+ /\bexample\b/i,
8680
+ /\bchangeme\b/i,
8681
+ /\breplace[_ -]?me\b/i,
8682
+ /\byour[_ -]?(api[_ -]?)?key\b/i,
8683
+ /\bapi[_ -]?key[_ -]?here\b/i,
8684
+ /\btoken[_ -]?here\b/i
8685
+ ];
8686
+ function normalizeUserConfigKey(value) {
8687
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1-$2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1-$2").replace(/[._/\s]+/g, "-").toLowerCase().replace(/[^a-z0-9-]+/g, "-").replace(/-+/g, "-").replace(/^-+|-+$/g, "");
8688
+ }
8689
+ function humanizeUserConfigLabel(value) {
8690
+ return value.replace(/([a-z0-9])([A-Z])/g, "$1 $2").replace(/([A-Z]+)([A-Z][a-z])/g, "$1 $2").replace(/[._/]+/g, " ").replace(/-/g, " ").split(/\s+/).filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1).toLowerCase()).join(" ");
8691
+ }
8692
+ function defaultUserConfigEnvVar(key) {
8693
+ return key.replace(/[^A-Za-z0-9]+/g, "_").replace(/_+/g, "_").replace(/^_+|_+$/g, "").toUpperCase();
8694
+ }
8695
+ function extractEnvReference(value) {
8696
+ if (!value) return void 0;
8697
+ const match = value.match(/^\$\{([A-Za-z_][A-Za-z0-9_]*)\}$/);
8698
+ return match?.[1];
8699
+ }
8700
+ function isPlaceholderSecretValue(value) {
8701
+ if (typeof value !== "string") return false;
8702
+ const normalized = value.trim();
8703
+ if (normalized === "") return false;
8704
+ return PLACEHOLDER_SECRET_PATTERNS.some((pattern) => pattern.test(normalized));
8705
+ }
8706
+ function isRuntimePlatformManaged(config, target, server) {
8707
+ if (server.transport === "stdio") return false;
8708
+ if (server.auth?.type === "platform") {
8709
+ return true;
8710
+ }
8711
+ if (target === "claude-code") {
8712
+ return config.platforms?.["claude-code"]?.mcpAuth === "platform";
8713
+ }
8714
+ if (target === "cursor") {
8715
+ return config.platforms?.cursor?.mcpAuth === "platform";
8716
+ }
8717
+ return false;
8718
+ }
8719
+ function dedupeUserConfigEntries(entries) {
8720
+ const deduped = [];
8721
+ const seenKeys = /* @__PURE__ */ new Set();
8722
+ const seenEnvVars = /* @__PURE__ */ new Set();
8723
+ for (const entry of entries) {
8724
+ const envVar = entry.envVar?.trim();
8725
+ if (seenKeys.has(entry.key)) continue;
8726
+ if (envVar && seenEnvVars.has(envVar)) continue;
8727
+ deduped.push(entry);
8728
+ seenKeys.add(entry.key);
8729
+ if (envVar) seenEnvVars.add(envVar);
8730
+ }
8731
+ return deduped;
8732
+ }
8733
+ function collectUserConfigEntries(config, platforms = config.targets) {
8734
+ const explicitEntries = (config.userConfig ?? []).filter((entry) => !entry.targets || entry.targets.some((target) => platforms.includes(target))).map((entry) => ({ ...entry, source: "explicit" }));
8735
+ const derivedEntries = [];
8736
+ for (const [serverName, server] of Object.entries(config.mcp ?? {})) {
8737
+ const applicableTargets = platforms.filter((target) => !isRuntimePlatformManaged(config, target, server));
8738
+ if (server.auth?.type && server.auth.type !== "none" && server.auth.type !== "platform" && ENV_VAR_NAME.test(server.auth.envVar) && applicableTargets.length > 0) {
8739
+ derivedEntries.push({
8740
+ key: normalizeUserConfigKey(server.auth.envVar),
8741
+ title: humanizeUserConfigLabel(server.auth.envVar),
8742
+ description: `Authentication credential for the ${serverName} MCP server.`,
8743
+ type: "secret",
8744
+ required: true,
8745
+ envVar: server.auth.envVar,
8746
+ targets: applicableTargets,
8747
+ source: "mcp-auth"
8748
+ });
8749
+ }
8750
+ if (server.transport === "stdio") {
8751
+ for (const [key, rawValue] of Object.entries(server.env ?? {})) {
8752
+ const envVar = extractEnvReference(rawValue) ?? (ENV_VAR_NAME.test(key) ? key : void 0);
8753
+ if (!envVar || !ENV_VAR_NAME.test(envVar)) continue;
8754
+ if (extractEnvReference(rawValue) === void 0 && rawValue !== "") {
8755
+ continue;
8756
+ }
8757
+ derivedEntries.push({
8758
+ key: normalizeUserConfigKey(envVar),
8759
+ title: humanizeUserConfigLabel(envVar),
8760
+ description: `Environment value required to launch the ${serverName} stdio MCP server.`,
8761
+ type: "secret",
8762
+ required: true,
8763
+ envVar,
8764
+ targets: applicableTargets.length > 0 ? applicableTargets : platforms,
8765
+ source: "mcp-env"
8766
+ });
8767
+ }
8768
+ }
8769
+ }
8770
+ return dedupeUserConfigEntries([
8771
+ ...explicitEntries,
8772
+ ...derivedEntries
8773
+ ]);
8774
+ }
8775
+
8776
+ // src/cli/publish.ts
8673
8777
  var INSTALLER_TARGETS = ["claude-code", "cursor", "codex", "opencode"];
8674
8778
  function runCommandDefault(command, args, options) {
8675
8779
  const result = spawnSync(command, args, {
@@ -8968,6 +9072,175 @@ echo
8968
9072
  echo "Installed __DISPLAY_NAME__ across ${installerTargets.join(", ")}."
8969
9073
  `.replaceAll("__REPO__", "REPO_PLACEHOLDER").replaceAll("__DISPLAY_NAME__", "DISPLAY_PLACEHOLDER");
8970
9074
  }
9075
+ function renderInstallerUserConfigSnippet(config, platform, installDirVariable) {
9076
+ const entries = collectUserConfigEntries(config, [platform]).map((entry) => ({
9077
+ key: entry.key,
9078
+ title: entry.title,
9079
+ type: entry.type ?? "string",
9080
+ required: entry.required !== false,
9081
+ envVar: entry.envVar ?? defaultUserConfigEnvVar(entry.key)
9082
+ }));
9083
+ if (entries.length === 0) return "";
9084
+ const promptLines = entries.map((entry) => {
9085
+ const functionName = entry.type === "secret" ? "pluxx_prompt_secret_config" : "pluxx_prompt_text_config";
9086
+ return `${functionName} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
9087
+ });
9088
+ return `
9089
+ PLUXX_USER_CONFIG_SPEC="$(cat <<'PLUXX_USER_CONFIG_JSON'
9090
+ ${JSON.stringify(entries)}
9091
+ PLUXX_USER_CONFIG_JSON
9092
+ )"
9093
+
9094
+ pluxx_is_placeholder_secret() {
9095
+ case "$1" in
9096
+ *dummy*|*Dummy*|*DUMMY*|*placeholder*|*Placeholder*|*PLACEHOLDER*|*changeme*|*CHANGE_ME*|*replace*me*|*Replace*Me*|*your*key*|*YOUR*KEY*|*api*key*here*|*API*KEY*HERE*|*token*here*|*TOKEN*HERE*)
9097
+ return 0
9098
+ ;;
9099
+ *)
9100
+ return 1
9101
+ ;;
9102
+ esac
9103
+ }
9104
+
9105
+ pluxx_prompt_secret_config() {
9106
+ local env_var="$1"
9107
+ local label="$2"
9108
+ local required="$3"
9109
+ local current_value="\${!env_var:-}"
9110
+
9111
+ if [[ -z "$current_value" && "$required" == "1" ]]; then
9112
+ if [[ -t 0 || -r /dev/tty ]]; then
9113
+ read -r -s -p "$label [$env_var]: " current_value </dev/tty
9114
+ echo >/dev/tty
9115
+ else
9116
+ echo "Missing required config: export $env_var before running this installer." >&2
9117
+ exit 1
9118
+ fi
9119
+ fi
9120
+
9121
+ if [[ -n "$current_value" ]] && pluxx_is_placeholder_secret "$current_value"; then
9122
+ echo "Refusing placeholder-looking secret for $env_var. Set a real value and rerun the installer." >&2
9123
+ exit 1
9124
+ fi
9125
+
9126
+ export "$env_var=$current_value"
9127
+ }
9128
+
9129
+ pluxx_prompt_text_config() {
9130
+ local env_var="$1"
9131
+ local label="$2"
9132
+ local required="$3"
9133
+ local current_value="\${!env_var:-}"
9134
+
9135
+ if [[ -z "$current_value" && "$required" == "1" ]]; then
9136
+ if [[ -t 0 || -r /dev/tty ]]; then
9137
+ read -r -p "$label [$env_var]: " current_value </dev/tty
9138
+ else
9139
+ echo "Missing required config: export $env_var before running this installer." >&2
9140
+ exit 1
9141
+ fi
9142
+ fi
9143
+
9144
+ export "$env_var=$current_value"
9145
+ }
9146
+
9147
+ ${promptLines.join("\n")}
9148
+
9149
+ export PLUXX_USER_CONFIG_SPEC
9150
+ export PLUXX_INSTALL_DIR="${installDirVariable}"
9151
+
9152
+ node <<'NODE'
9153
+ const fs = require('fs')
9154
+ const path = require('path')
9155
+
9156
+ const installDir = process.env.PLUXX_INSTALL_DIR
9157
+ const spec = JSON.parse(process.env.PLUXX_USER_CONFIG_SPEC || '[]')
9158
+
9159
+ if (installDir && spec.length > 0) {
9160
+ const env = {}
9161
+ const values = {}
9162
+
9163
+ for (const entry of spec) {
9164
+ const value = process.env[entry.envVar]
9165
+ if (value === undefined || value === '') continue
9166
+ values[entry.key] = value
9167
+ env[entry.envVar] = value
9168
+ }
9169
+
9170
+ fs.writeFileSync(
9171
+ path.join(installDir, '.pluxx-user.json'),
9172
+ JSON.stringify({ values, env }, null, 2) + '\\n',
9173
+ )
9174
+
9175
+ const envScriptPath = path.join(installDir, 'scripts/check-env.sh')
9176
+ if (fs.existsSync(envScriptPath)) {
9177
+ fs.writeFileSync(
9178
+ envScriptPath,
9179
+ '#!/usr/bin/env bash\\nset -euo pipefail\\n# pluxx install materialized required config for this local plugin install.\\nexit 0\\n',
9180
+ )
9181
+ }
9182
+
9183
+ const materialize = (value) =>
9184
+ typeof value === 'string'
9185
+ ? value.replace(/\\$\\{([A-Za-z_][A-Za-z0-9_]*)\\}/g, (_match, name) => env[name] || '${" + name + "}')
9186
+ : value
9187
+
9188
+ const materializeRecord = (record) => {
9189
+ if (!record || typeof record !== 'object') return record
9190
+ const next = {}
9191
+ for (const [key, value] of Object.entries(record)) {
9192
+ next[key] = materialize(value)
9193
+ }
9194
+ return next
9195
+ }
9196
+
9197
+ for (const relativePath of ['.mcp.json', 'mcp.json']) {
9198
+ const filepath = path.join(installDir, relativePath)
9199
+ if (!fs.existsSync(filepath)) continue
9200
+
9201
+ const payload = JSON.parse(fs.readFileSync(filepath, 'utf8'))
9202
+ for (const server of Object.values(payload.mcpServers || {})) {
9203
+ if (!server || typeof server !== 'object') continue
9204
+
9205
+ if (server.env) {
9206
+ server.env = materializeRecord(server.env)
9207
+ }
9208
+
9209
+ if (server.bearer_token_env_var && env[server.bearer_token_env_var]) {
9210
+ server.http_headers = {
9211
+ ...(server.http_headers || {}),
9212
+ Authorization: 'Bearer ' + env[server.bearer_token_env_var],
9213
+ }
9214
+ delete server.bearer_token_env_var
9215
+ }
9216
+
9217
+ if (server.env_http_headers && typeof server.env_http_headers === 'object') {
9218
+ server.http_headers = {
9219
+ ...(server.http_headers || {}),
9220
+ }
9221
+ for (const [headerName, envVar] of Object.entries(server.env_http_headers)) {
9222
+ if (env[envVar]) server.http_headers[headerName] = env[envVar]
9223
+ }
9224
+ delete server.env_http_headers
9225
+ }
9226
+
9227
+ if (server.headers) {
9228
+ server.headers = materializeRecord(server.headers)
9229
+ }
9230
+ if (server.http_headers) {
9231
+ server.http_headers = materializeRecord(server.http_headers)
9232
+ }
9233
+ }
9234
+
9235
+ fs.writeFileSync(filepath, JSON.stringify(payload, null, 2) + '\\n')
9236
+ }
9237
+ }
9238
+ NODE
9239
+ `;
9240
+ }
9241
+ function hasInstallerUserConfig(config, platform) {
9242
+ return collectUserConfigEntries(config, [platform]).length > 0;
9243
+ }
8971
9244
  function renderInstallClaudeCodeScript(config) {
8972
9245
  return `#!/usr/bin/env bash
8973
9246
  set -euo pipefail
@@ -8994,6 +9267,7 @@ need_cmd tar
8994
9267
  need_cmd mktemp
8995
9268
  need_cmd grep
8996
9269
  need_cmd sed
9270
+ ${hasInstallerUserConfig(config, "claude-code") ? "need_cmd node" : ""}
8997
9271
 
8998
9272
  if [[ "$SKIP_INSTALL" != "1" ]]; then
8999
9273
  need_cmd curl
@@ -9030,6 +9304,7 @@ DESCRIPTION="$(grep -E '"description"' "$PLUGIN_MANIFEST" | head -n1 | sed -E 's
9030
9304
  mkdir -p "$INSTALL_ROOT/.claude-plugin" "$INSTALL_ROOT/plugins"
9031
9305
  rm -rf "$INSTALL_ROOT/plugins/$PLUGIN_NAME"
9032
9306
  cp -R "$BUNDLE_DIR" "$INSTALL_ROOT/plugins/$PLUGIN_NAME"
9307
+ ${renderInstallerUserConfigSnippet(config, "claude-code", "$INSTALL_ROOT/plugins/$PLUGIN_NAME")}
9033
9308
 
9034
9309
  cat > "$INSTALL_ROOT/.claude-plugin/marketplace.json" <<JSON
9035
9310
  {
@@ -9093,6 +9368,7 @@ need_cmd() {
9093
9368
  need_cmd tar
9094
9369
  need_cmd mktemp
9095
9370
  need_cmd curl
9371
+ ${hasInstallerUserConfig(config, "cursor") ? "need_cmd node" : ""}
9096
9372
 
9097
9373
  TMP_DIR="$(mktemp -d)"
9098
9374
  cleanup() {
@@ -9121,6 +9397,7 @@ fi
9121
9397
  mkdir -p "$(dirname "$INSTALL_DIR")"
9122
9398
  rm -rf "$INSTALL_DIR"
9123
9399
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
9400
+ ${renderInstallerUserConfigSnippet(config, "cursor", "$INSTALL_DIR")}
9124
9401
 
9125
9402
  echo "Installed $PLUGIN_NAME to $INSTALL_DIR"
9126
9403
  echo "If Cursor is already open, use Developer: Reload Window or restart Cursor so the plugin is picked up."
@@ -9178,6 +9455,7 @@ fi
9178
9455
  mkdir -p "$(dirname "$INSTALL_DIR")"
9179
9456
  rm -rf "$INSTALL_DIR"
9180
9457
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
9458
+ ${renderInstallerUserConfigSnippet(config, "codex", "$INSTALL_DIR")}
9181
9459
 
9182
9460
  mkdir -p "$(dirname "$MARKETPLACE_PATH")"
9183
9461
 
@@ -9292,6 +9570,7 @@ fi
9292
9570
  mkdir -p "$(dirname "$INSTALL_DIR")" "$SKILLS_ROOT"
9293
9571
  rm -rf "$INSTALL_DIR"
9294
9572
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
9573
+ ${renderInstallerUserConfigSnippet(config, "opencode", "$INSTALL_DIR")}
9295
9574
 
9296
9575
  export ENTRY_PATH
9297
9576
  export PLUGIN_NAME
@@ -9573,7 +9852,7 @@ function runPublish(config, options = {}) {
9573
9852
  }
9574
9853
 
9575
9854
  // src/cli/verify-install.ts
9576
- import { existsSync as existsSync5 } from "fs";
9855
+ import { existsSync as existsSync5, lstatSync as lstatSync2, readdirSync as readdirSync3, readFileSync as readFileSync5, readlinkSync, realpathSync, statSync } from "fs";
9577
9856
  import { resolve as resolve5 } from "path";
9578
9857
 
9579
9858
  // src/cli/doctor.ts
@@ -9656,16 +9935,144 @@ function getInstallTargets(pluginName) {
9656
9935
  function getClaudeMarketplaceName(pluginName) {
9657
9936
  return `pluxx-local-${pluginName}`;
9658
9937
  }
9659
- function getClaudeMarketplaceRoot(pluginName) {
9660
- const home = process.env.HOME ?? "~";
9661
- return resolve3(home, ".claude/plugins/data", getClaudeMarketplaceName(pluginName));
9662
- }
9663
9938
  function resolveInstalledConsumerPath(target, pluginName) {
9664
- if (target.platform === "claude-code") {
9665
- return resolve3(getClaudeMarketplaceRoot(pluginName), "plugins", pluginName);
9939
+ if (target.platform === "claude-code" && pluginName !== "") {
9940
+ return target.pluginDir;
9666
9941
  }
9667
9942
  return target.pluginDir;
9668
9943
  }
9944
+ function manifestPathForPlatform(platform) {
9945
+ switch (platform) {
9946
+ case "claude-code":
9947
+ return ".claude-plugin/plugin.json";
9948
+ case "cursor":
9949
+ return ".cursor-plugin/plugin.json";
9950
+ case "codex":
9951
+ return ".codex-plugin/plugin.json";
9952
+ case "opencode":
9953
+ return "package.json";
9954
+ default:
9955
+ return void 0;
9956
+ }
9957
+ }
9958
+ function isRelativeBundlePath(value) {
9959
+ return value.startsWith("./") || value.startsWith("../") || value.startsWith(".\\") || value.startsWith("..\\");
9960
+ }
9961
+ function resolveBundleReference(rootDir, value) {
9962
+ if (isRelativeBundlePath(value)) {
9963
+ return resolve3(rootDir, value);
9964
+ }
9965
+ const pluginRootMatch = value.match(/^\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/](.+)$/);
9966
+ if (pluginRootMatch) {
9967
+ return resolve3(rootDir, pluginRootMatch[1]);
9968
+ }
9969
+ return void 0;
9970
+ }
9971
+ function readBundleManifestReferences(manifest) {
9972
+ const references = [];
9973
+ for (const key of ["commands", "skills", "hooks", "mcpServers"]) {
9974
+ const value = manifest[key];
9975
+ if (typeof value === "string") {
9976
+ references.push(value);
9977
+ }
9978
+ }
9979
+ const agents = manifest.agents;
9980
+ if (typeof agents === "string") {
9981
+ references.push(agents);
9982
+ } else if (Array.isArray(agents)) {
9983
+ for (const entry of agents) {
9984
+ if (typeof entry === "string") {
9985
+ references.push(entry);
9986
+ }
9987
+ }
9988
+ }
9989
+ return references;
9990
+ }
9991
+ function collectHookCommandStrings(value, commands) {
9992
+ if (Array.isArray(value)) {
9993
+ for (const entry of value) {
9994
+ collectHookCommandStrings(entry, commands);
9995
+ }
9996
+ return;
9997
+ }
9998
+ if (!value || typeof value !== "object") return;
9999
+ for (const [key, child] of Object.entries(value)) {
10000
+ if (key === "command" && typeof child === "string") {
10001
+ commands.push(child);
10002
+ continue;
10003
+ }
10004
+ collectHookCommandStrings(child, commands);
10005
+ }
10006
+ }
10007
+ function extractBundleCommandTargets(command) {
10008
+ const matches = command.match(/\$\{(?:CLAUDE_PLUGIN_ROOT|CURSOR_PLUGIN_ROOT|PLUGIN_ROOT)\}[\\/][^\s"'`;$|&<>]+|\.\.?[\\/][^\s"'`;$|&<>]+/g);
10009
+ return matches ?? [];
10010
+ }
10011
+ function findInstalledBundleIntegrityIssues(rootDir, platform) {
10012
+ const manifestPath = manifestPathForPlatform(platform);
10013
+ if (!manifestPath) {
10014
+ return {
10015
+ missingManifestPaths: [],
10016
+ missingHookTargets: []
10017
+ };
10018
+ }
10019
+ const manifestFile = resolve3(rootDir, manifestPath);
10020
+ if (!existsSync3(manifestFile)) {
10021
+ return {
10022
+ manifestIssue: `missing plugin manifest at ${manifestPath}`,
10023
+ missingManifestPaths: [],
10024
+ missingHookTargets: []
10025
+ };
10026
+ }
10027
+ let manifest;
10028
+ try {
10029
+ manifest = JSON.parse(readFileSync3(manifestFile, "utf-8"));
10030
+ } catch (error) {
10031
+ return {
10032
+ manifestIssue: `plugin manifest at ${manifestPath} is not parseable: ${error instanceof Error ? error.message : String(error)}`,
10033
+ missingManifestPaths: [],
10034
+ missingHookTargets: []
10035
+ };
10036
+ }
10037
+ const missingManifestPaths = readBundleManifestReferences(manifest).filter((value) => {
10038
+ const resolved = resolveBundleReference(rootDir, value);
10039
+ return resolved !== void 0 && !existsSync3(resolved);
10040
+ }).sort();
10041
+ const hooksReference = typeof manifest.hooks === "string" ? manifest.hooks : void 0;
10042
+ if (!hooksReference) {
10043
+ return {
10044
+ missingManifestPaths,
10045
+ missingHookTargets: []
10046
+ };
10047
+ }
10048
+ const hooksPath = resolveBundleReference(rootDir, hooksReference);
10049
+ if (!hooksPath || !existsSync3(hooksPath)) {
10050
+ return {
10051
+ missingManifestPaths,
10052
+ missingHookTargets: []
10053
+ };
10054
+ }
10055
+ try {
10056
+ const hooks = JSON.parse(readFileSync3(hooksPath, "utf-8"));
10057
+ const commands = [];
10058
+ collectHookCommandStrings(hooks, commands);
10059
+ const missingHookTargets = [...new Set(
10060
+ commands.flatMap(extractBundleCommandTargets).filter((value) => {
10061
+ const resolved = resolveBundleReference(rootDir, value);
10062
+ return resolved !== void 0 && !existsSync3(resolved);
10063
+ })
10064
+ )].sort();
10065
+ return {
10066
+ missingManifestPaths,
10067
+ missingHookTargets
10068
+ };
10069
+ } catch {
10070
+ return {
10071
+ missingManifestPaths,
10072
+ missingHookTargets: []
10073
+ };
10074
+ }
10075
+ }
9669
10076
  function planInstallPlugin(distDir, pluginName, platforms) {
9670
10077
  const targets = getInstallTargets(pluginName);
9671
10078
  const filtered = platforms ? targets.filter((t) => platforms.includes(t.platform)) : targets;
@@ -9753,6 +10160,42 @@ var WORKFLOW_SKILL_DEFINITIONS = [
9753
10160
  title: "General Research",
9754
10161
  description: "Handle broad search and query workflows when there is not a more specific product surface match.",
9755
10162
  match: ["search", "query", "lookup", "look up", "discover", "find"]
10163
+ },
10164
+ {
10165
+ key: "web-research",
10166
+ title: "Web Research",
10167
+ description: "Search the web, fetch pages, and synthesize public source-backed research.",
10168
+ match: ["web", "website", "url", "page", "fetch", "crawl", "scrape", "search", "result", "source"]
10169
+ },
10170
+ {
10171
+ key: "source-review",
10172
+ title: "Source Review",
10173
+ description: "Audit sources, citations, evidence quality, and duplicate or weak research results.",
10174
+ match: ["source", "citation", "evidence", "audit", "review", "quality", "rank", "dedupe", "duplicate"]
10175
+ },
10176
+ {
10177
+ key: "content-extraction",
10178
+ title: "Content Extraction",
10179
+ description: "Extract structured content, entities, and summaries from pages, documents, or search results.",
10180
+ match: ["extract", "parse", "content", "summary", "summarize", "document", "html", "markdown"]
10181
+ },
10182
+ {
10183
+ key: "knowledge-search",
10184
+ title: "Knowledge Search",
10185
+ description: "Search docs, knowledge bases, papers, repositories, and internal reference material.",
10186
+ match: ["docs", "documentation", "knowledge", "paper", "papers", "repo", "repository", "api", "reference"]
10187
+ },
10188
+ {
10189
+ key: "news-monitoring",
10190
+ title: "News Monitoring",
10191
+ description: "Find recent news, launches, announcements, and time-bounded developments.",
10192
+ match: ["news", "recent", "latest", "launch", "announcement", "announced", "date", "published"]
10193
+ },
10194
+ {
10195
+ key: "code-research",
10196
+ title: "Code Research",
10197
+ description: "Find implementation docs, code examples, API usage, migration notes, and troubleshooting context.",
10198
+ match: ["code", "sdk", "api", "github", "example", "migration", "error", "troubleshoot", "implementation"]
9756
10199
  }
9757
10200
  ];
9758
10201
  var WORKFLOW_SKILL_DEFINITION_BY_KEY = new Map(
@@ -9945,6 +10388,10 @@ function checkInstalledUserConfig(checks, rootDir) {
9945
10388
  const payload = JSON.parse(readFileSync4(resolvedPath, "utf-8"));
9946
10389
  const valueCount = Object.keys(payload.values ?? {}).length;
9947
10390
  const envCount = Object.keys(payload.env ?? {}).length;
10391
+ const placeholderKeys = [
10392
+ ...Object.entries(payload.values ?? {}).filter(([, value]) => isPlaceholderSecretValue(value)).map(([key]) => key),
10393
+ ...Object.entries(payload.env ?? {}).filter(([, value]) => isPlaceholderSecretValue(value)).map(([key]) => key)
10394
+ ];
9948
10395
  addCheck(checks, {
9949
10396
  level: "success",
9950
10397
  code: "consumer-user-config-valid",
@@ -9953,6 +10400,16 @@ function checkInstalledUserConfig(checks, rootDir) {
9953
10400
  fix: "No action needed.",
9954
10401
  path: userConfigPath
9955
10402
  });
10403
+ if (placeholderKeys.length > 0) {
10404
+ addCheck(checks, {
10405
+ level: "warning",
10406
+ code: "consumer-user-config-placeholder-secret",
10407
+ title: "Local install config contains placeholder-looking secret values",
10408
+ detail: `.pluxx-user.json contains placeholder-looking value${placeholderKeys.length === 1 ? "" : "s"} for ${placeholderKeys.join(", ")}.`,
10409
+ fix: "Reinstall the plugin with real secret values, or edit .pluxx-user.json and refresh/restart the host.",
10410
+ path: userConfigPath
10411
+ });
10412
+ }
9956
10413
  } catch (error) {
9957
10414
  addCheck(checks, {
9958
10415
  level: "error",
@@ -10037,6 +10494,16 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
10037
10494
  if (servers.length === 0) {
10038
10495
  return;
10039
10496
  }
10497
+ if (layout.platform === "codex") {
10498
+ addCheck(checks, {
10499
+ level: "info",
10500
+ code: "consumer-codex-mcp-bundled-visibility",
10501
+ title: "Codex plugin-bundled MCP visibility clarified",
10502
+ detail: `This Codex plugin bundle includes ${servers.length} MCP server${servers.length === 1 ? "" : "s"} through ${layout.mcpConfigPath}. Codex may show this on the plugin detail page without listing it on the global MCP servers settings page.`,
10503
+ fix: "Use the plugin detail page, tool availability in chat, and `pluxx verify-install --target codex` as the source of truth for plugin-bundled MCP wiring. If the MCP remains unavailable after install, use Plugins > Refresh if present or restart Codex.",
10504
+ path: layout.mcpConfigPath
10505
+ });
10506
+ }
10040
10507
  const remoteEntries = servers.filter((server) => "url" in server);
10041
10508
  const stdioEntries = servers.filter((server) => "command" in server);
10042
10509
  const inlineHeaderEntries = servers.filter((server) => {
@@ -10098,6 +10565,38 @@ function checkInstalledMcpConfig(checks, rootDir, layout) {
10098
10565
  });
10099
10566
  }
10100
10567
  }
10568
+ function checkInstalledBundleIntegrity(checks, rootDir, layout) {
10569
+ const issues = findInstalledBundleIntegrityIssues(rootDir, layout.platform);
10570
+ const details = [];
10571
+ if (issues.manifestIssue) {
10572
+ details.push(issues.manifestIssue);
10573
+ }
10574
+ if (issues.missingManifestPaths.length > 0) {
10575
+ details.push(`manifest references missing path${issues.missingManifestPaths.length === 1 ? "" : "s"}: ${issues.missingManifestPaths.join(", ")}`);
10576
+ }
10577
+ if (issues.missingHookTargets.length > 0) {
10578
+ details.push(`hook commands reference missing bundle target${issues.missingHookTargets.length === 1 ? "" : "s"}: ${issues.missingHookTargets.join(", ")}`);
10579
+ }
10580
+ if (details.length === 0) {
10581
+ addCheck(checks, {
10582
+ level: "success",
10583
+ code: "consumer-bundle-integrity-valid",
10584
+ title: "Installed bundle references resolve inside the plugin",
10585
+ detail: "Every manifest-declared path and bundle-relative hook target exists in this installed bundle.",
10586
+ fix: "No action needed.",
10587
+ path: layout.manifestPath
10588
+ });
10589
+ return;
10590
+ }
10591
+ addCheck(checks, {
10592
+ level: "error",
10593
+ code: "consumer-bundle-integrity-invalid",
10594
+ title: "Installed bundle is missing referenced files",
10595
+ detail: details.join("; "),
10596
+ fix: "Reinstall the plugin or rebuild the bundle so every manifest path and hook target exists in the installed plugin.",
10597
+ path: layout.manifestPath
10598
+ });
10599
+ }
10101
10600
  function findMissingInstalledStdioRuntimePaths(rootDir, stdioEntries) {
10102
10601
  const missing = /* @__PURE__ */ new Set();
10103
10602
  for (const server of stdioEntries) {
@@ -10283,6 +10782,7 @@ async function doctorConsumer(rootDir = process.cwd()) {
10283
10782
  path: layout.manifestPath
10284
10783
  });
10285
10784
  checkConsumerManifest(checks, rootDir, layout);
10785
+ checkInstalledBundleIntegrity(checks, rootDir, layout);
10286
10786
  checkInstalledUserConfig(checks, rootDir);
10287
10787
  checkInstalledEnvValidation(checks, rootDir);
10288
10788
  checkInstalledMcpConfig(checks, rootDir, layout);
@@ -10296,18 +10796,103 @@ async function doctorConsumer(rootDir = process.cwd()) {
10296
10796
  // src/cli/verify-install.ts
10297
10797
  function buildCheckFromReport(target, pluginName, report) {
10298
10798
  const consumerPath = resolveInstalledConsumerPath(target, pluginName);
10799
+ const staleReason = target.built && existsSync5(consumerPath) ? detectStaleInstall(target, pluginName, consumerPath) : void 0;
10800
+ const stale = staleReason !== void 0;
10299
10801
  return {
10300
10802
  platform: target.platform,
10301
10803
  installPath: target.pluginDir,
10302
10804
  consumerPath,
10303
10805
  built: target.built,
10304
10806
  installed: existsSync5(consumerPath),
10305
- ok: report.errors === 0,
10306
- errors: report.errors,
10807
+ stale,
10808
+ ...staleReason ? { staleReason } : {},
10809
+ ok: report.errors === 0 && !stale,
10810
+ errors: report.errors + (stale ? 1 : 0),
10307
10811
  warnings: report.warnings,
10308
10812
  infos: report.infos
10309
10813
  };
10310
10814
  }
10815
+ function manifestPathForPlatform2(platform) {
10816
+ switch (platform) {
10817
+ case "claude-code":
10818
+ return ".claude-plugin/plugin.json";
10819
+ case "cursor":
10820
+ return ".cursor-plugin/plugin.json";
10821
+ case "codex":
10822
+ return ".codex-plugin/plugin.json";
10823
+ case "opencode":
10824
+ return "package.json";
10825
+ default:
10826
+ return void 0;
10827
+ }
10828
+ }
10829
+ function readInstalledManifestVersion(rootDir, platform) {
10830
+ const manifestPath = manifestPathForPlatform2(platform);
10831
+ if (!manifestPath) return void 0;
10832
+ const filepath = resolve5(rootDir, manifestPath);
10833
+ if (!existsSync5(filepath)) return void 0;
10834
+ try {
10835
+ const manifest = JSON.parse(readFileSync5(filepath, "utf-8"));
10836
+ return typeof manifest.version === "string" ? manifest.version : void 0;
10837
+ } catch {
10838
+ return void 0;
10839
+ }
10840
+ }
10841
+ function findCodexCacheCandidates(pluginName) {
10842
+ const home = process.env.HOME ?? "~";
10843
+ const cacheRoot = resolve5(home, ".codex/plugins/cache");
10844
+ if (!existsSync5(cacheRoot)) return [];
10845
+ const candidates = [];
10846
+ for (const marketplace of readdirSync3(cacheRoot)) {
10847
+ const pluginRoot = resolve5(cacheRoot, marketplace, pluginName);
10848
+ if (!existsSync5(pluginRoot)) continue;
10849
+ for (const versionDir of readdirSync3(pluginRoot)) {
10850
+ const candidatePath = resolve5(pluginRoot, versionDir);
10851
+ try {
10852
+ const stats = statSync(candidatePath);
10853
+ if (!stats.isDirectory()) continue;
10854
+ candidates.push({
10855
+ path: candidatePath,
10856
+ version: readInstalledManifestVersion(candidatePath, "codex") ?? versionDir,
10857
+ mtimeMs: stats.mtimeMs
10858
+ });
10859
+ } catch {
10860
+ }
10861
+ }
10862
+ }
10863
+ return candidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
10864
+ }
10865
+ function detectCodexCacheStaleness(pluginName, builtVersion) {
10866
+ if (!builtVersion) return void 0;
10867
+ const candidates = findCodexCacheCandidates(pluginName);
10868
+ if (candidates.length === 0) return void 0;
10869
+ if (candidates.some((candidate) => candidate.version === builtVersion)) return void 0;
10870
+ const newest = candidates[0];
10871
+ return `Codex active cache appears stale at ${newest.path}; cached version ${newest.version ?? "unknown"} does not match built version ${builtVersion}. Use Plugins > Refresh if available, or restart/reinstall Codex to load the current plugin bundle.`;
10872
+ }
10873
+ function detectStaleInstall(target, pluginName, consumerPath) {
10874
+ try {
10875
+ const details = lstatSync2(consumerPath);
10876
+ if (details.isSymbolicLink()) {
10877
+ const installedRealPath = realpathSync(consumerPath);
10878
+ const builtRealPath = realpathSync(target.sourceDir);
10879
+ if (installedRealPath !== builtRealPath) {
10880
+ return `installed symlink points to ${readlinkSync(consumerPath)}, not the current build at ${target.sourceDir}`;
10881
+ }
10882
+ }
10883
+ } catch {
10884
+ return void 0;
10885
+ }
10886
+ const builtVersion = readInstalledManifestVersion(target.sourceDir, target.platform);
10887
+ const installedVersion = readInstalledManifestVersion(consumerPath, target.platform);
10888
+ if (builtVersion && installedVersion && builtVersion !== installedVersion) {
10889
+ return `installed version ${installedVersion} does not match built version ${builtVersion}`;
10890
+ }
10891
+ if (target.platform === "codex") {
10892
+ return detectCodexCacheStaleness(pluginName, builtVersion);
10893
+ }
10894
+ return void 0;
10895
+ }
10311
10896
  async function verifyInstall(config, options = {}) {
10312
10897
  const rootDir = options.rootDir ?? process.cwd();
10313
10898
  const distDir = resolve5(rootDir, config.outDir);
@@ -10335,9 +10920,36 @@ function printVerifyInstallResult(result) {
10335
10920
  console.log(`${prefix} ${check.platform}: ${check.consumerPath}`);
10336
10921
  console.log(` install path: ${check.installPath}`);
10337
10922
  console.log(` built: ${check.built ? "yes" : "no"}; installed: ${check.installed ? "yes" : "no"}; errors: ${check.errors}; warnings: ${check.warnings}; infos: ${check.infos}`);
10923
+ if (check.stale) {
10924
+ console.log(` stale install: ${check.staleReason}`);
10925
+ }
10926
+ if (!check.ok) {
10927
+ for (const action of getVerifyInstallRecoveryActions(check)) {
10928
+ console.log(` fix: ${action}`);
10929
+ }
10930
+ }
10338
10931
  }
10339
10932
  console.log(result.ok ? "pluxx verify-install passed." : "pluxx verify-install failed.");
10340
10933
  }
10934
+ function getVerifyInstallRecoveryActions(check) {
10935
+ const actions = [];
10936
+ if (!check.built) {
10937
+ actions.push(`run pluxx build --target ${check.platform}`);
10938
+ }
10939
+ if (check.built && !check.installed) {
10940
+ actions.push(`run pluxx install --target ${check.platform}${check.platform === "claude-code" ? " and accept/trust any hook prompt if expected" : ""}`);
10941
+ }
10942
+ if (check.stale) {
10943
+ actions.push(`rerun pluxx install --target ${check.platform} to replace the stale local install`);
10944
+ if (check.platform === "codex") {
10945
+ actions.push("in Codex, use Plugins > Refresh if available, or restart Codex so the plugin cache reloads");
10946
+ }
10947
+ }
10948
+ if (check.errors > 0 && actions.length === 0) {
10949
+ actions.push(`run pluxx doctor --consumer "${check.consumerPath}" for the detailed host-specific failure`);
10950
+ }
10951
+ return actions;
10952
+ }
10341
10953
  export {
10342
10954
  CORE_FOUR_PRIMITIVE_CAPABILITIES,
10343
10955
  PLATFORM_LIMITS,