@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/cli/doctor.d.ts.map +1 -1
- package/dist/cli/eval.d.ts.map +1 -1
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +771 -45
- package/dist/cli/init-from-mcp.d.ts.map +1 -1
- package/dist/cli/install.d.ts +6 -0
- package/dist/cli/install.d.ts.map +1 -1
- package/dist/cli/publish.d.ts.map +1 -1
- package/dist/cli/verify-install.d.ts +2 -0
- package/dist/cli/verify-install.d.ts.map +1 -1
- package/dist/index.js +621 -9
- package/dist/user-config.d.ts +1 -0
- package/dist/user-config.d.ts.map +1 -1
- package/package.json +2 -2
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
|
|
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
|
-
|
|
10306
|
-
|
|
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,
|