@orchid-labs/pluxx 0.1.18 → 0.1.20

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/cli/index.js CHANGED
@@ -24455,17 +24455,18 @@ function renderInstallerUserConfigSnippet(config, platform, installDirVariable)
24455
24455
  if (entries.length === 0) return "";
24456
24456
  const promptLines = entries.map((entry) => {
24457
24457
  const functionName = entry.type === "secret" ? "pluxx_prompt_secret_config" : "pluxx_prompt_text_config";
24458
- return `${functionName} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
24458
+ return `${functionName} ${JSON.stringify(entry.key)} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
24459
24459
  });
24460
24460
  return `
24461
24461
  PLUXX_USER_CONFIG_SPEC="$(cat <<'PLUXX_USER_CONFIG_JSON'
24462
24462
  ${JSON.stringify(entries)}
24463
24463
  PLUXX_USER_CONFIG_JSON
24464
24464
  )"
24465
+ PLUXX_REUSED_USER_CONFIG=0
24465
24466
 
24466
24467
  pluxx_is_placeholder_secret() {
24467
24468
  case "$1" in
24468
- *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*)
24469
+ *dummy*|*Dummy*|*DUMMY*|*placeholder*|*Placeholder*|*PLACEHOLDER*|*example*|*Example*|*EXAMPLE*|*changeme*|*CHANGE_ME*|*replace*me*|*Replace*Me*|*your*key*|*YOUR*KEY*|*api*key*here*|*API*KEY*HERE*|*token*here*|*TOKEN*HERE*)
24469
24470
  return 0
24470
24471
  ;;
24471
24472
  *)
@@ -24474,12 +24475,61 @@ pluxx_is_placeholder_secret() {
24474
24475
  esac
24475
24476
  }
24476
24477
 
24478
+ pluxx_saved_config_value() {
24479
+ local key="$1"
24480
+ local env_var="$2"
24481
+
24482
+ if [[ -z "\${PLUXX_SAVED_USER_CONFIG_PATH:-}" || ! -f "$PLUXX_SAVED_USER_CONFIG_PATH" ]]; then
24483
+ return 1
24484
+ fi
24485
+
24486
+ PLUXX_SAVED_CONFIG_KEY="$key" PLUXX_SAVED_CONFIG_ENV_VAR="$env_var" node <<'NODE'
24487
+ const fs = require('fs')
24488
+
24489
+ const filepath = process.env.PLUXX_SAVED_USER_CONFIG_PATH
24490
+ const key = process.env.PLUXX_SAVED_CONFIG_KEY
24491
+ const envVar = process.env.PLUXX_SAVED_CONFIG_ENV_VAR
24492
+
24493
+ try {
24494
+ const payload = JSON.parse(fs.readFileSync(filepath, 'utf8'))
24495
+ const candidates = [
24496
+ payload && payload.env && envVar ? payload.env[envVar] : undefined,
24497
+ payload && payload.values && key ? payload.values[key] : undefined,
24498
+ ]
24499
+
24500
+ for (const candidate of candidates) {
24501
+ if (candidate === undefined || candidate === null) continue
24502
+ if (!['string', 'number', 'boolean'].includes(typeof candidate)) continue
24503
+ const value = String(candidate)
24504
+ if (value === '') continue
24505
+ process.stdout.write(value)
24506
+ process.exit(0)
24507
+ }
24508
+ } catch {}
24509
+
24510
+ process.exit(1)
24511
+ NODE
24512
+ }
24513
+
24477
24514
  pluxx_prompt_secret_config() {
24478
- local env_var="$1"
24479
- local label="$2"
24480
- local required="$3"
24515
+ local key="$1"
24516
+ local env_var="$2"
24517
+ local label="$3"
24518
+ local required="$4"
24481
24519
  local current_value="\${!env_var:-}"
24482
24520
 
24521
+ if [[ -z "$current_value" && "\${PLUXX_RECONFIGURE:-0}" != "1" ]]; then
24522
+ local saved_value=""
24523
+ if saved_value="$(pluxx_saved_config_value "$key" "$env_var")"; then
24524
+ if pluxx_is_placeholder_secret "$saved_value"; then
24525
+ echo "Ignoring placeholder-looking saved config for $env_var." >&2
24526
+ else
24527
+ current_value="$saved_value"
24528
+ PLUXX_REUSED_USER_CONFIG=1
24529
+ fi
24530
+ fi
24531
+ fi
24532
+
24483
24533
  if [[ -z "$current_value" && "$required" == "1" ]]; then
24484
24534
  if [[ -t 0 || -r /dev/tty ]]; then
24485
24535
  read -r -s -p "$label [$env_var]: " current_value </dev/tty
@@ -24499,11 +24549,20 @@ pluxx_prompt_secret_config() {
24499
24549
  }
24500
24550
 
24501
24551
  pluxx_prompt_text_config() {
24502
- local env_var="$1"
24503
- local label="$2"
24504
- local required="$3"
24552
+ local key="$1"
24553
+ local env_var="$2"
24554
+ local label="$3"
24555
+ local required="$4"
24505
24556
  local current_value="\${!env_var:-}"
24506
24557
 
24558
+ if [[ -z "$current_value" && "\${PLUXX_RECONFIGURE:-0}" != "1" ]]; then
24559
+ local saved_value=""
24560
+ if saved_value="$(pluxx_saved_config_value "$key" "$env_var")"; then
24561
+ current_value="$saved_value"
24562
+ PLUXX_REUSED_USER_CONFIG=1
24563
+ fi
24564
+ fi
24565
+
24507
24566
  if [[ -z "$current_value" && "$required" == "1" ]]; then
24508
24567
  if [[ -t 0 || -r /dev/tty ]]; then
24509
24568
  read -r -p "$label [$env_var]: " current_value </dev/tty
@@ -24518,6 +24577,10 @@ pluxx_prompt_text_config() {
24518
24577
 
24519
24578
  ${promptLines.join("\n")}
24520
24579
 
24580
+ if [[ "$PLUXX_REUSED_USER_CONFIG" == "1" ]]; then
24581
+ echo "Found existing plugin config; reusing saved install values."
24582
+ fi
24583
+
24521
24584
  export PLUXX_USER_CONFIG_SPEC
24522
24585
  export PLUXX_INSTALL_DIR="${installDirVariable}"
24523
24586
 
@@ -24613,6 +24676,17 @@ NODE
24613
24676
  function hasInstallerUserConfig(config, platform) {
24614
24677
  return collectUserConfigEntries(config, [platform]).length > 0;
24615
24678
  }
24679
+ function renderInstallerSavedUserConfigCaptureSnippet(config, platform, installDirVariable) {
24680
+ if (!hasInstallerUserConfig(config, platform)) return "";
24681
+ return `
24682
+ PLUXX_SAVED_USER_CONFIG_PATH=""
24683
+ if [[ "\${PLUXX_RECONFIGURE:-0}" != "1" && -f "${installDirVariable}/.pluxx-user.json" ]]; then
24684
+ PLUXX_SAVED_USER_CONFIG_PATH="$TMP_DIR/pluxx-saved-user-config.json"
24685
+ cp "${installDirVariable}/.pluxx-user.json" "$PLUXX_SAVED_USER_CONFIG_PATH"
24686
+ fi
24687
+ export PLUXX_SAVED_USER_CONFIG_PATH
24688
+ `;
24689
+ }
24616
24690
  function renderInstallerMcpPathMaterializationSnippet(platform, installDirVariable) {
24617
24691
  if (platform !== "codex") return "";
24618
24692
  return `
@@ -24681,6 +24755,247 @@ if [[ -f "${installDirVariable}/scripts/bootstrap-runtime.sh" ]]; then
24681
24755
  fi
24682
24756
  `;
24683
24757
  }
24758
+ function renderInstallerCodexPluginHooksSnippet(installDirVariable) {
24759
+ return `
24760
+ export PLUXX_INSTALL_DIR="${installDirVariable}"
24761
+
24762
+ set +e
24763
+ node <<'NODE'
24764
+ const fs = require('fs')
24765
+ const path = require('path')
24766
+
24767
+ const installDir = process.env.PLUXX_INSTALL_DIR
24768
+ if (!installDir) process.exit(0)
24769
+
24770
+ const manifestPath = path.join(installDir, '.codex-plugin/plugin.json')
24771
+ const standardHooksPath = path.join(installDir, 'hooks/hooks.json')
24772
+ let hasPluginHooks = fs.existsSync(standardHooksPath)
24773
+
24774
+ if (fs.existsSync(manifestPath)) {
24775
+ try {
24776
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
24777
+ const manifestHooks = manifest.hooks
24778
+ hasPluginHooks ||= typeof manifestHooks === 'string' && manifestHooks.trim().length > 0
24779
+ hasPluginHooks ||= Array.isArray(manifestHooks) && manifestHooks.length > 0
24780
+ hasPluginHooks ||= manifestHooks && typeof manifestHooks === 'object' && Object.keys(manifestHooks).length > 0
24781
+ hasPluginHooks ||= manifestHooks === true
24782
+ } catch {}
24783
+ }
24784
+
24785
+ process.exit(hasPluginHooks ? 0 : 2)
24786
+ NODE
24787
+ PLUXX_CODEX_BUNDLE_HAS_HOOKS="$?"
24788
+ set -e
24789
+
24790
+ if [[ "$PLUXX_CODEX_BUNDLE_HAS_HOOKS" == "0" ]]; then
24791
+ CODEX_HOME_DIR="\${CODEX_HOME:-$HOME/.codex}"
24792
+ CODEX_CONFIG_PATH="\${PLUXX_CODEX_CONFIG_PATH:-$CODEX_HOME_DIR/config.toml}"
24793
+ PLUXX_CODEX_HOOKS_MODE="\${PLUXX_CODEX_ENABLE_PLUGIN_HOOKS:-prompt}"
24794
+
24795
+ export CODEX_CONFIG_PATH
24796
+ if node <<'NODE'
24797
+ const fs = require('fs')
24798
+ const filepath = process.env.CODEX_CONFIG_PATH
24799
+
24800
+ function stripTomlComment(line) {
24801
+ let quote = null
24802
+ let escaped = false
24803
+ for (let index = 0; index < line.length; index += 1) {
24804
+ const char = line[index]
24805
+ if (escaped) {
24806
+ escaped = false
24807
+ continue
24808
+ }
24809
+ if (quote && char === '\\\\') {
24810
+ escaped = true
24811
+ continue
24812
+ }
24813
+ if (char === '"' || char === "'") {
24814
+ quote = quote === char ? null : quote || char
24815
+ continue
24816
+ }
24817
+ if (!quote && char === '#') return line.slice(0, index)
24818
+ }
24819
+ return line
24820
+ }
24821
+
24822
+ function isTomlTrue(rawValue) {
24823
+ return /^true\\b/i.test(rawValue.trim())
24824
+ }
24825
+
24826
+ let text = ''
24827
+ try {
24828
+ text = fs.readFileSync(filepath, 'utf8')
24829
+ } catch {
24830
+ process.exit(1)
24831
+ }
24832
+ const lines = text.split(/\\r?\\n/)
24833
+ let tableName = ''
24834
+ for (const line of lines) {
24835
+ const trimmed = stripTomlComment(line).trim()
24836
+ if (!trimmed) continue
24837
+ const tableMatch = trimmed.match(/^\\[([^\\]]+)\\]$/)
24838
+ if (tableMatch) {
24839
+ tableName = tableMatch[1].trim()
24840
+ continue
24841
+ }
24842
+ if (tableName === '') {
24843
+ const dottedMatch = trimmed.match(/^features\\.plugin_hooks\\s*=\\s*(.+)$/)
24844
+ if (dottedMatch && isTomlTrue(dottedMatch[1])) process.exit(0)
24845
+ const inlineMatch = trimmed.match(/^features\\s*=\\s*(.+)$/)
24846
+ if (inlineMatch && /\\bplugin_hooks\\s*=\\s*true\\b/i.test(inlineMatch[1])) process.exit(0)
24847
+ }
24848
+ if (tableName !== 'features') continue
24849
+ const match = trimmed.match(/^plugin_hooks\\s*=\\s*(.+)$/)
24850
+ if (match && isTomlTrue(match[1])) process.exit(0)
24851
+ }
24852
+ process.exit(1)
24853
+ NODE
24854
+ then
24855
+ echo "Codex plugin-bundled hooks already enabled in $CODEX_CONFIG_PATH."
24856
+ else
24857
+ PLUXX_ENABLE_CODEX_HOOKS="0"
24858
+ case "$PLUXX_CODEX_HOOKS_MODE" in
24859
+ 1|true|TRUE|yes|YES|always|ALWAYS)
24860
+ PLUXX_ENABLE_CODEX_HOOKS="1"
24861
+ ;;
24862
+ 0|false|FALSE|no|NO|never|NEVER|skip|SKIP)
24863
+ PLUXX_ENABLE_CODEX_HOOKS="0"
24864
+ ;;
24865
+ *)
24866
+ if [[ -r /dev/tty ]]; then
24867
+ echo "This Codex plugin bundle includes startup hooks." >/dev/tty
24868
+ echo "Codex requires [features].plugin_hooks = true before plugin-bundled hooks can run." >/dev/tty
24869
+ read -r -p "Enable Codex plugin-bundled hooks in $CODEX_CONFIG_PATH now? [Y/n] " PLUXX_CODEX_HOOKS_REPLY </dev/tty
24870
+ case "$PLUXX_CODEX_HOOKS_REPLY" in
24871
+ n|N|no|NO)
24872
+ PLUXX_ENABLE_CODEX_HOOKS="0"
24873
+ ;;
24874
+ *)
24875
+ PLUXX_ENABLE_CODEX_HOOKS="1"
24876
+ ;;
24877
+ esac
24878
+ fi
24879
+ ;;
24880
+ esac
24881
+
24882
+ if [[ "$PLUXX_ENABLE_CODEX_HOOKS" == "1" ]]; then
24883
+ mkdir -p "$(dirname "$CODEX_CONFIG_PATH")"
24884
+ node <<'NODE'
24885
+ const fs = require('fs')
24886
+ const path = require('path')
24887
+
24888
+ const filepath = process.env.CODEX_CONFIG_PATH
24889
+
24890
+ function stripTomlComment(line) {
24891
+ let quote = null
24892
+ let escaped = false
24893
+ for (let index = 0; index < line.length; index += 1) {
24894
+ const char = line[index]
24895
+ if (escaped) {
24896
+ escaped = false
24897
+ continue
24898
+ }
24899
+ if (quote && char === '\\\\') {
24900
+ escaped = true
24901
+ continue
24902
+ }
24903
+ if (char === '"' || char === "'") {
24904
+ quote = quote === char ? null : quote || char
24905
+ continue
24906
+ }
24907
+ if (!quote && char === '#') return line.slice(0, index)
24908
+ }
24909
+ return line
24910
+ }
24911
+
24912
+ let text = ''
24913
+ try {
24914
+ text = fs.readFileSync(filepath, 'utf8')
24915
+ } catch {}
24916
+
24917
+ const lines = text.split(/\\r?\\n/)
24918
+ if (lines.length === 1 && lines[0] === '') lines.pop()
24919
+
24920
+ let start = -1
24921
+ let end = lines.length
24922
+ let firstTopLevelFeaturesDotted = -1
24923
+ let topLevelPluginHooksDotted = -1
24924
+ let topLevelInlineFeatures = -1
24925
+ let tableName = ''
24926
+ for (let index = 0; index < lines.length; index += 1) {
24927
+ const trimmed = stripTomlComment(lines[index]).trim()
24928
+ const tableMatch = trimmed.match(/^\\[([^\\]]+)\\]$/)
24929
+ if (tableMatch) tableName = tableMatch[1].trim()
24930
+
24931
+ if (trimmed === '[features]') {
24932
+ start = index
24933
+ break
24934
+ }
24935
+
24936
+ if (tableName === '') {
24937
+ if (/^features\\.[A-Za-z0-9_-]+\\s*=/.test(trimmed) && firstTopLevelFeaturesDotted < 0) {
24938
+ firstTopLevelFeaturesDotted = index
24939
+ }
24940
+ if (/^features\\.plugin_hooks\\s*=/.test(trimmed)) {
24941
+ topLevelPluginHooksDotted = index
24942
+ }
24943
+ if (/^features\\s*=\\s*\\{/.test(trimmed)) {
24944
+ topLevelInlineFeatures = index
24945
+ }
24946
+ }
24947
+ }
24948
+
24949
+ if (start >= 0) {
24950
+ for (let index = start + 1; index < lines.length; index += 1) {
24951
+ if (/^\\s*\\[[^\\]]+\\]/.test(stripTomlComment(lines[index]))) {
24952
+ end = index
24953
+ break
24954
+ }
24955
+ }
24956
+
24957
+ let updated = false
24958
+ for (let index = start + 1; index < end; index += 1) {
24959
+ if (/^plugin_hooks\\s*=/.test(stripTomlComment(lines[index]).trim())) {
24960
+ lines[index] = 'plugin_hooks = true'
24961
+ updated = true
24962
+ }
24963
+ }
24964
+ if (!updated) lines.splice(start + 1, 0, 'plugin_hooks = true')
24965
+ } else if (topLevelPluginHooksDotted >= 0) {
24966
+ lines[topLevelPluginHooksDotted] = 'features.plugin_hooks = true'
24967
+ } else if (firstTopLevelFeaturesDotted >= 0) {
24968
+ lines.splice(firstTopLevelFeaturesDotted + 1, 0, 'features.plugin_hooks = true')
24969
+ } else if (topLevelInlineFeatures >= 0 && lines[topLevelInlineFeatures].includes('}')) {
24970
+ if (/\\bplugin_hooks\\s*=/.test(lines[topLevelInlineFeatures])) {
24971
+ lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(
24972
+ /\\bplugin_hooks\\s*=\\s*(true|false)\\b/i,
24973
+ 'plugin_hooks = true',
24974
+ )
24975
+ } else {
24976
+ lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(/}/, ', plugin_hooks = true }')
24977
+ }
24978
+ } else {
24979
+ if (lines.length > 0 && lines[lines.length - 1] !== '') lines.push('')
24980
+ lines.push('[features]', 'plugin_hooks = true')
24981
+ }
24982
+
24983
+ fs.mkdirSync(path.dirname(filepath), { recursive: true })
24984
+ fs.writeFileSync(filepath, lines.join('\\n') + '\\n')
24985
+ NODE
24986
+ echo "Enabled Codex plugin-bundled hooks in $CODEX_CONFIG_PATH."
24987
+ echo "Restart or refresh Codex before relying on plugin startup hooks."
24988
+ else
24989
+ echo "Codex plugin-bundled hooks are not enabled. Startup hooks from this plugin will not run until you add this to $CODEX_CONFIG_PATH:" >&2
24990
+ echo "[features]" >&2
24991
+ echo "plugin_hooks = true" >&2
24992
+ echo "Then restart or refresh Codex before relying on plugin startup hooks." >&2
24993
+ echo "Set PLUXX_CODEX_ENABLE_PLUGIN_HOOKS=1 before running this installer to enable it noninteractively." >&2
24994
+ fi
24995
+ fi
24996
+ fi
24997
+ `;
24998
+ }
24684
24999
  function renderInstallClaudeCodeScript(config) {
24685
25000
  return `#!/usr/bin/env bash
24686
25001
  set -euo pipefail
@@ -24742,6 +25057,7 @@ VERSION="$(grep -E '"version"' "$PLUGIN_MANIFEST" | head -n1 | sed -E 's/.*"vers
24742
25057
  DESCRIPTION="$(grep -E '"description"' "$PLUGIN_MANIFEST" | head -n1 | sed -E 's/.*"description"[[:space:]]*:[[:space:]]*"([^"]+)".*/\\1/')"
24743
25058
 
24744
25059
  mkdir -p "$INSTALL_ROOT/.claude-plugin" "$INSTALL_ROOT/plugins"
25060
+ ${renderInstallerSavedUserConfigCaptureSnippet(config, "claude-code", "$INSTALL_ROOT/plugins/$PLUGIN_NAME")}
24745
25061
  rm -rf "$INSTALL_ROOT/plugins/$PLUGIN_NAME"
24746
25062
  cp -R "$BUNDLE_DIR" "$INSTALL_ROOT/plugins/$PLUGIN_NAME"
24747
25063
  ${renderInstallerUserConfigSnippet(config, "claude-code", "$INSTALL_ROOT/plugins/$PLUGIN_NAME")}
@@ -24837,6 +25153,7 @@ if [[ ! -f "$PLUGIN_MANIFEST" ]]; then
24837
25153
  fi
24838
25154
 
24839
25155
  mkdir -p "$(dirname "$INSTALL_DIR")"
25156
+ ${renderInstallerSavedUserConfigCaptureSnippet(config, "cursor", "$INSTALL_DIR")}
24840
25157
  rm -rf "$INSTALL_DIR"
24841
25158
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
24842
25159
  ${renderInstallerUserConfigSnippet(config, "cursor", "$INSTALL_DIR")}
@@ -24897,11 +25214,13 @@ if [[ ! -f "$PLUGIN_MANIFEST" ]]; then
24897
25214
  fi
24898
25215
 
24899
25216
  mkdir -p "$(dirname "$INSTALL_DIR")"
25217
+ ${renderInstallerSavedUserConfigCaptureSnippet(config, "codex", "$INSTALL_DIR")}
24900
25218
  rm -rf "$INSTALL_DIR"
24901
25219
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
24902
25220
  ${renderInstallerUserConfigSnippet(config, "codex", "$INSTALL_DIR")}
24903
25221
  ${renderInstallerMcpPathMaterializationSnippet("codex", "$INSTALL_DIR")}
24904
25222
  ${renderInstallerRuntimeBootstrapSnippet("$INSTALL_DIR")}
25223
+ ${renderInstallerCodexPluginHooksSnippet("$INSTALL_DIR")}
24905
25224
 
24906
25225
  mkdir -p "$(dirname "$MARKETPLACE_PATH")"
24907
25226
 
@@ -25014,6 +25333,7 @@ if [[ ! -f "$PLUGIN_PACKAGE" ]]; then
25014
25333
  fi
25015
25334
 
25016
25335
  mkdir -p "$(dirname "$INSTALL_DIR")" "$SKILLS_ROOT"
25336
+ ${renderInstallerSavedUserConfigCaptureSnippet(config, "opencode", "$INSTALL_DIR")}
25017
25337
  rm -rf "$INSTALL_DIR"
25018
25338
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
25019
25339
  ${renderInstallerUserConfigSnippet(config, "opencode", "$INSTALL_DIR")}
@@ -1 +1 @@
1
- {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/cli/publish.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"publish.d.ts","sourceRoot":"","sources":["../../src/cli/publish.ts"],"names":[],"mappings":"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"}
package/dist/index.js CHANGED
@@ -9449,17 +9449,18 @@ function renderInstallerUserConfigSnippet(config, platform, installDirVariable)
9449
9449
  if (entries.length === 0) return "";
9450
9450
  const promptLines = entries.map((entry) => {
9451
9451
  const functionName = entry.type === "secret" ? "pluxx_prompt_secret_config" : "pluxx_prompt_text_config";
9452
- return `${functionName} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
9452
+ return `${functionName} ${JSON.stringify(entry.key)} ${JSON.stringify(entry.envVar)} ${JSON.stringify(entry.title)} ${entry.required ? "1" : "0"}`;
9453
9453
  });
9454
9454
  return `
9455
9455
  PLUXX_USER_CONFIG_SPEC="$(cat <<'PLUXX_USER_CONFIG_JSON'
9456
9456
  ${JSON.stringify(entries)}
9457
9457
  PLUXX_USER_CONFIG_JSON
9458
9458
  )"
9459
+ PLUXX_REUSED_USER_CONFIG=0
9459
9460
 
9460
9461
  pluxx_is_placeholder_secret() {
9461
9462
  case "$1" in
9462
- *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*)
9463
+ *dummy*|*Dummy*|*DUMMY*|*placeholder*|*Placeholder*|*PLACEHOLDER*|*example*|*Example*|*EXAMPLE*|*changeme*|*CHANGE_ME*|*replace*me*|*Replace*Me*|*your*key*|*YOUR*KEY*|*api*key*here*|*API*KEY*HERE*|*token*here*|*TOKEN*HERE*)
9463
9464
  return 0
9464
9465
  ;;
9465
9466
  *)
@@ -9468,12 +9469,61 @@ pluxx_is_placeholder_secret() {
9468
9469
  esac
9469
9470
  }
9470
9471
 
9472
+ pluxx_saved_config_value() {
9473
+ local key="$1"
9474
+ local env_var="$2"
9475
+
9476
+ if [[ -z "\${PLUXX_SAVED_USER_CONFIG_PATH:-}" || ! -f "$PLUXX_SAVED_USER_CONFIG_PATH" ]]; then
9477
+ return 1
9478
+ fi
9479
+
9480
+ PLUXX_SAVED_CONFIG_KEY="$key" PLUXX_SAVED_CONFIG_ENV_VAR="$env_var" node <<'NODE'
9481
+ const fs = require('fs')
9482
+
9483
+ const filepath = process.env.PLUXX_SAVED_USER_CONFIG_PATH
9484
+ const key = process.env.PLUXX_SAVED_CONFIG_KEY
9485
+ const envVar = process.env.PLUXX_SAVED_CONFIG_ENV_VAR
9486
+
9487
+ try {
9488
+ const payload = JSON.parse(fs.readFileSync(filepath, 'utf8'))
9489
+ const candidates = [
9490
+ payload && payload.env && envVar ? payload.env[envVar] : undefined,
9491
+ payload && payload.values && key ? payload.values[key] : undefined,
9492
+ ]
9493
+
9494
+ for (const candidate of candidates) {
9495
+ if (candidate === undefined || candidate === null) continue
9496
+ if (!['string', 'number', 'boolean'].includes(typeof candidate)) continue
9497
+ const value = String(candidate)
9498
+ if (value === '') continue
9499
+ process.stdout.write(value)
9500
+ process.exit(0)
9501
+ }
9502
+ } catch {}
9503
+
9504
+ process.exit(1)
9505
+ NODE
9506
+ }
9507
+
9471
9508
  pluxx_prompt_secret_config() {
9472
- local env_var="$1"
9473
- local label="$2"
9474
- local required="$3"
9509
+ local key="$1"
9510
+ local env_var="$2"
9511
+ local label="$3"
9512
+ local required="$4"
9475
9513
  local current_value="\${!env_var:-}"
9476
9514
 
9515
+ if [[ -z "$current_value" && "\${PLUXX_RECONFIGURE:-0}" != "1" ]]; then
9516
+ local saved_value=""
9517
+ if saved_value="$(pluxx_saved_config_value "$key" "$env_var")"; then
9518
+ if pluxx_is_placeholder_secret "$saved_value"; then
9519
+ echo "Ignoring placeholder-looking saved config for $env_var." >&2
9520
+ else
9521
+ current_value="$saved_value"
9522
+ PLUXX_REUSED_USER_CONFIG=1
9523
+ fi
9524
+ fi
9525
+ fi
9526
+
9477
9527
  if [[ -z "$current_value" && "$required" == "1" ]]; then
9478
9528
  if [[ -t 0 || -r /dev/tty ]]; then
9479
9529
  read -r -s -p "$label [$env_var]: " current_value </dev/tty
@@ -9493,11 +9543,20 @@ pluxx_prompt_secret_config() {
9493
9543
  }
9494
9544
 
9495
9545
  pluxx_prompt_text_config() {
9496
- local env_var="$1"
9497
- local label="$2"
9498
- local required="$3"
9546
+ local key="$1"
9547
+ local env_var="$2"
9548
+ local label="$3"
9549
+ local required="$4"
9499
9550
  local current_value="\${!env_var:-}"
9500
9551
 
9552
+ if [[ -z "$current_value" && "\${PLUXX_RECONFIGURE:-0}" != "1" ]]; then
9553
+ local saved_value=""
9554
+ if saved_value="$(pluxx_saved_config_value "$key" "$env_var")"; then
9555
+ current_value="$saved_value"
9556
+ PLUXX_REUSED_USER_CONFIG=1
9557
+ fi
9558
+ fi
9559
+
9501
9560
  if [[ -z "$current_value" && "$required" == "1" ]]; then
9502
9561
  if [[ -t 0 || -r /dev/tty ]]; then
9503
9562
  read -r -p "$label [$env_var]: " current_value </dev/tty
@@ -9512,6 +9571,10 @@ pluxx_prompt_text_config() {
9512
9571
 
9513
9572
  ${promptLines.join("\n")}
9514
9573
 
9574
+ if [[ "$PLUXX_REUSED_USER_CONFIG" == "1" ]]; then
9575
+ echo "Found existing plugin config; reusing saved install values."
9576
+ fi
9577
+
9515
9578
  export PLUXX_USER_CONFIG_SPEC
9516
9579
  export PLUXX_INSTALL_DIR="${installDirVariable}"
9517
9580
 
@@ -9607,6 +9670,17 @@ NODE
9607
9670
  function hasInstallerUserConfig(config, platform) {
9608
9671
  return collectUserConfigEntries(config, [platform]).length > 0;
9609
9672
  }
9673
+ function renderInstallerSavedUserConfigCaptureSnippet(config, platform, installDirVariable) {
9674
+ if (!hasInstallerUserConfig(config, platform)) return "";
9675
+ return `
9676
+ PLUXX_SAVED_USER_CONFIG_PATH=""
9677
+ if [[ "\${PLUXX_RECONFIGURE:-0}" != "1" && -f "${installDirVariable}/.pluxx-user.json" ]]; then
9678
+ PLUXX_SAVED_USER_CONFIG_PATH="$TMP_DIR/pluxx-saved-user-config.json"
9679
+ cp "${installDirVariable}/.pluxx-user.json" "$PLUXX_SAVED_USER_CONFIG_PATH"
9680
+ fi
9681
+ export PLUXX_SAVED_USER_CONFIG_PATH
9682
+ `;
9683
+ }
9610
9684
  function renderInstallerMcpPathMaterializationSnippet(platform, installDirVariable) {
9611
9685
  if (platform !== "codex") return "";
9612
9686
  return `
@@ -9675,6 +9749,247 @@ if [[ -f "${installDirVariable}/scripts/bootstrap-runtime.sh" ]]; then
9675
9749
  fi
9676
9750
  `;
9677
9751
  }
9752
+ function renderInstallerCodexPluginHooksSnippet(installDirVariable) {
9753
+ return `
9754
+ export PLUXX_INSTALL_DIR="${installDirVariable}"
9755
+
9756
+ set +e
9757
+ node <<'NODE'
9758
+ const fs = require('fs')
9759
+ const path = require('path')
9760
+
9761
+ const installDir = process.env.PLUXX_INSTALL_DIR
9762
+ if (!installDir) process.exit(0)
9763
+
9764
+ const manifestPath = path.join(installDir, '.codex-plugin/plugin.json')
9765
+ const standardHooksPath = path.join(installDir, 'hooks/hooks.json')
9766
+ let hasPluginHooks = fs.existsSync(standardHooksPath)
9767
+
9768
+ if (fs.existsSync(manifestPath)) {
9769
+ try {
9770
+ const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8'))
9771
+ const manifestHooks = manifest.hooks
9772
+ hasPluginHooks ||= typeof manifestHooks === 'string' && manifestHooks.trim().length > 0
9773
+ hasPluginHooks ||= Array.isArray(manifestHooks) && manifestHooks.length > 0
9774
+ hasPluginHooks ||= manifestHooks && typeof manifestHooks === 'object' && Object.keys(manifestHooks).length > 0
9775
+ hasPluginHooks ||= manifestHooks === true
9776
+ } catch {}
9777
+ }
9778
+
9779
+ process.exit(hasPluginHooks ? 0 : 2)
9780
+ NODE
9781
+ PLUXX_CODEX_BUNDLE_HAS_HOOKS="$?"
9782
+ set -e
9783
+
9784
+ if [[ "$PLUXX_CODEX_BUNDLE_HAS_HOOKS" == "0" ]]; then
9785
+ CODEX_HOME_DIR="\${CODEX_HOME:-$HOME/.codex}"
9786
+ CODEX_CONFIG_PATH="\${PLUXX_CODEX_CONFIG_PATH:-$CODEX_HOME_DIR/config.toml}"
9787
+ PLUXX_CODEX_HOOKS_MODE="\${PLUXX_CODEX_ENABLE_PLUGIN_HOOKS:-prompt}"
9788
+
9789
+ export CODEX_CONFIG_PATH
9790
+ if node <<'NODE'
9791
+ const fs = require('fs')
9792
+ const filepath = process.env.CODEX_CONFIG_PATH
9793
+
9794
+ function stripTomlComment(line) {
9795
+ let quote = null
9796
+ let escaped = false
9797
+ for (let index = 0; index < line.length; index += 1) {
9798
+ const char = line[index]
9799
+ if (escaped) {
9800
+ escaped = false
9801
+ continue
9802
+ }
9803
+ if (quote && char === '\\\\') {
9804
+ escaped = true
9805
+ continue
9806
+ }
9807
+ if (char === '"' || char === "'") {
9808
+ quote = quote === char ? null : quote || char
9809
+ continue
9810
+ }
9811
+ if (!quote && char === '#') return line.slice(0, index)
9812
+ }
9813
+ return line
9814
+ }
9815
+
9816
+ function isTomlTrue(rawValue) {
9817
+ return /^true\\b/i.test(rawValue.trim())
9818
+ }
9819
+
9820
+ let text = ''
9821
+ try {
9822
+ text = fs.readFileSync(filepath, 'utf8')
9823
+ } catch {
9824
+ process.exit(1)
9825
+ }
9826
+ const lines = text.split(/\\r?\\n/)
9827
+ let tableName = ''
9828
+ for (const line of lines) {
9829
+ const trimmed = stripTomlComment(line).trim()
9830
+ if (!trimmed) continue
9831
+ const tableMatch = trimmed.match(/^\\[([^\\]]+)\\]$/)
9832
+ if (tableMatch) {
9833
+ tableName = tableMatch[1].trim()
9834
+ continue
9835
+ }
9836
+ if (tableName === '') {
9837
+ const dottedMatch = trimmed.match(/^features\\.plugin_hooks\\s*=\\s*(.+)$/)
9838
+ if (dottedMatch && isTomlTrue(dottedMatch[1])) process.exit(0)
9839
+ const inlineMatch = trimmed.match(/^features\\s*=\\s*(.+)$/)
9840
+ if (inlineMatch && /\\bplugin_hooks\\s*=\\s*true\\b/i.test(inlineMatch[1])) process.exit(0)
9841
+ }
9842
+ if (tableName !== 'features') continue
9843
+ const match = trimmed.match(/^plugin_hooks\\s*=\\s*(.+)$/)
9844
+ if (match && isTomlTrue(match[1])) process.exit(0)
9845
+ }
9846
+ process.exit(1)
9847
+ NODE
9848
+ then
9849
+ echo "Codex plugin-bundled hooks already enabled in $CODEX_CONFIG_PATH."
9850
+ else
9851
+ PLUXX_ENABLE_CODEX_HOOKS="0"
9852
+ case "$PLUXX_CODEX_HOOKS_MODE" in
9853
+ 1|true|TRUE|yes|YES|always|ALWAYS)
9854
+ PLUXX_ENABLE_CODEX_HOOKS="1"
9855
+ ;;
9856
+ 0|false|FALSE|no|NO|never|NEVER|skip|SKIP)
9857
+ PLUXX_ENABLE_CODEX_HOOKS="0"
9858
+ ;;
9859
+ *)
9860
+ if [[ -r /dev/tty ]]; then
9861
+ echo "This Codex plugin bundle includes startup hooks." >/dev/tty
9862
+ echo "Codex requires [features].plugin_hooks = true before plugin-bundled hooks can run." >/dev/tty
9863
+ read -r -p "Enable Codex plugin-bundled hooks in $CODEX_CONFIG_PATH now? [Y/n] " PLUXX_CODEX_HOOKS_REPLY </dev/tty
9864
+ case "$PLUXX_CODEX_HOOKS_REPLY" in
9865
+ n|N|no|NO)
9866
+ PLUXX_ENABLE_CODEX_HOOKS="0"
9867
+ ;;
9868
+ *)
9869
+ PLUXX_ENABLE_CODEX_HOOKS="1"
9870
+ ;;
9871
+ esac
9872
+ fi
9873
+ ;;
9874
+ esac
9875
+
9876
+ if [[ "$PLUXX_ENABLE_CODEX_HOOKS" == "1" ]]; then
9877
+ mkdir -p "$(dirname "$CODEX_CONFIG_PATH")"
9878
+ node <<'NODE'
9879
+ const fs = require('fs')
9880
+ const path = require('path')
9881
+
9882
+ const filepath = process.env.CODEX_CONFIG_PATH
9883
+
9884
+ function stripTomlComment(line) {
9885
+ let quote = null
9886
+ let escaped = false
9887
+ for (let index = 0; index < line.length; index += 1) {
9888
+ const char = line[index]
9889
+ if (escaped) {
9890
+ escaped = false
9891
+ continue
9892
+ }
9893
+ if (quote && char === '\\\\') {
9894
+ escaped = true
9895
+ continue
9896
+ }
9897
+ if (char === '"' || char === "'") {
9898
+ quote = quote === char ? null : quote || char
9899
+ continue
9900
+ }
9901
+ if (!quote && char === '#') return line.slice(0, index)
9902
+ }
9903
+ return line
9904
+ }
9905
+
9906
+ let text = ''
9907
+ try {
9908
+ text = fs.readFileSync(filepath, 'utf8')
9909
+ } catch {}
9910
+
9911
+ const lines = text.split(/\\r?\\n/)
9912
+ if (lines.length === 1 && lines[0] === '') lines.pop()
9913
+
9914
+ let start = -1
9915
+ let end = lines.length
9916
+ let firstTopLevelFeaturesDotted = -1
9917
+ let topLevelPluginHooksDotted = -1
9918
+ let topLevelInlineFeatures = -1
9919
+ let tableName = ''
9920
+ for (let index = 0; index < lines.length; index += 1) {
9921
+ const trimmed = stripTomlComment(lines[index]).trim()
9922
+ const tableMatch = trimmed.match(/^\\[([^\\]]+)\\]$/)
9923
+ if (tableMatch) tableName = tableMatch[1].trim()
9924
+
9925
+ if (trimmed === '[features]') {
9926
+ start = index
9927
+ break
9928
+ }
9929
+
9930
+ if (tableName === '') {
9931
+ if (/^features\\.[A-Za-z0-9_-]+\\s*=/.test(trimmed) && firstTopLevelFeaturesDotted < 0) {
9932
+ firstTopLevelFeaturesDotted = index
9933
+ }
9934
+ if (/^features\\.plugin_hooks\\s*=/.test(trimmed)) {
9935
+ topLevelPluginHooksDotted = index
9936
+ }
9937
+ if (/^features\\s*=\\s*\\{/.test(trimmed)) {
9938
+ topLevelInlineFeatures = index
9939
+ }
9940
+ }
9941
+ }
9942
+
9943
+ if (start >= 0) {
9944
+ for (let index = start + 1; index < lines.length; index += 1) {
9945
+ if (/^\\s*\\[[^\\]]+\\]/.test(stripTomlComment(lines[index]))) {
9946
+ end = index
9947
+ break
9948
+ }
9949
+ }
9950
+
9951
+ let updated = false
9952
+ for (let index = start + 1; index < end; index += 1) {
9953
+ if (/^plugin_hooks\\s*=/.test(stripTomlComment(lines[index]).trim())) {
9954
+ lines[index] = 'plugin_hooks = true'
9955
+ updated = true
9956
+ }
9957
+ }
9958
+ if (!updated) lines.splice(start + 1, 0, 'plugin_hooks = true')
9959
+ } else if (topLevelPluginHooksDotted >= 0) {
9960
+ lines[topLevelPluginHooksDotted] = 'features.plugin_hooks = true'
9961
+ } else if (firstTopLevelFeaturesDotted >= 0) {
9962
+ lines.splice(firstTopLevelFeaturesDotted + 1, 0, 'features.plugin_hooks = true')
9963
+ } else if (topLevelInlineFeatures >= 0 && lines[topLevelInlineFeatures].includes('}')) {
9964
+ if (/\\bplugin_hooks\\s*=/.test(lines[topLevelInlineFeatures])) {
9965
+ lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(
9966
+ /\\bplugin_hooks\\s*=\\s*(true|false)\\b/i,
9967
+ 'plugin_hooks = true',
9968
+ )
9969
+ } else {
9970
+ lines[topLevelInlineFeatures] = lines[topLevelInlineFeatures].replace(/}/, ', plugin_hooks = true }')
9971
+ }
9972
+ } else {
9973
+ if (lines.length > 0 && lines[lines.length - 1] !== '') lines.push('')
9974
+ lines.push('[features]', 'plugin_hooks = true')
9975
+ }
9976
+
9977
+ fs.mkdirSync(path.dirname(filepath), { recursive: true })
9978
+ fs.writeFileSync(filepath, lines.join('\\n') + '\\n')
9979
+ NODE
9980
+ echo "Enabled Codex plugin-bundled hooks in $CODEX_CONFIG_PATH."
9981
+ echo "Restart or refresh Codex before relying on plugin startup hooks."
9982
+ else
9983
+ echo "Codex plugin-bundled hooks are not enabled. Startup hooks from this plugin will not run until you add this to $CODEX_CONFIG_PATH:" >&2
9984
+ echo "[features]" >&2
9985
+ echo "plugin_hooks = true" >&2
9986
+ echo "Then restart or refresh Codex before relying on plugin startup hooks." >&2
9987
+ echo "Set PLUXX_CODEX_ENABLE_PLUGIN_HOOKS=1 before running this installer to enable it noninteractively." >&2
9988
+ fi
9989
+ fi
9990
+ fi
9991
+ `;
9992
+ }
9678
9993
  function renderInstallClaudeCodeScript(config) {
9679
9994
  return `#!/usr/bin/env bash
9680
9995
  set -euo pipefail
@@ -9736,6 +10051,7 @@ VERSION="$(grep -E '"version"' "$PLUGIN_MANIFEST" | head -n1 | sed -E 's/.*"vers
9736
10051
  DESCRIPTION="$(grep -E '"description"' "$PLUGIN_MANIFEST" | head -n1 | sed -E 's/.*"description"[[:space:]]*:[[:space:]]*"([^"]+)".*/\\1/')"
9737
10052
 
9738
10053
  mkdir -p "$INSTALL_ROOT/.claude-plugin" "$INSTALL_ROOT/plugins"
10054
+ ${renderInstallerSavedUserConfigCaptureSnippet(config, "claude-code", "$INSTALL_ROOT/plugins/$PLUGIN_NAME")}
9739
10055
  rm -rf "$INSTALL_ROOT/plugins/$PLUGIN_NAME"
9740
10056
  cp -R "$BUNDLE_DIR" "$INSTALL_ROOT/plugins/$PLUGIN_NAME"
9741
10057
  ${renderInstallerUserConfigSnippet(config, "claude-code", "$INSTALL_ROOT/plugins/$PLUGIN_NAME")}
@@ -9831,6 +10147,7 @@ if [[ ! -f "$PLUGIN_MANIFEST" ]]; then
9831
10147
  fi
9832
10148
 
9833
10149
  mkdir -p "$(dirname "$INSTALL_DIR")"
10150
+ ${renderInstallerSavedUserConfigCaptureSnippet(config, "cursor", "$INSTALL_DIR")}
9834
10151
  rm -rf "$INSTALL_DIR"
9835
10152
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
9836
10153
  ${renderInstallerUserConfigSnippet(config, "cursor", "$INSTALL_DIR")}
@@ -9891,11 +10208,13 @@ if [[ ! -f "$PLUGIN_MANIFEST" ]]; then
9891
10208
  fi
9892
10209
 
9893
10210
  mkdir -p "$(dirname "$INSTALL_DIR")"
10211
+ ${renderInstallerSavedUserConfigCaptureSnippet(config, "codex", "$INSTALL_DIR")}
9894
10212
  rm -rf "$INSTALL_DIR"
9895
10213
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
9896
10214
  ${renderInstallerUserConfigSnippet(config, "codex", "$INSTALL_DIR")}
9897
10215
  ${renderInstallerMcpPathMaterializationSnippet("codex", "$INSTALL_DIR")}
9898
10216
  ${renderInstallerRuntimeBootstrapSnippet("$INSTALL_DIR")}
10217
+ ${renderInstallerCodexPluginHooksSnippet("$INSTALL_DIR")}
9899
10218
 
9900
10219
  mkdir -p "$(dirname "$MARKETPLACE_PATH")"
9901
10220
 
@@ -10008,6 +10327,7 @@ if [[ ! -f "$PLUGIN_PACKAGE" ]]; then
10008
10327
  fi
10009
10328
 
10010
10329
  mkdir -p "$(dirname "$INSTALL_DIR")" "$SKILLS_ROOT"
10330
+ ${renderInstallerSavedUserConfigCaptureSnippet(config, "opencode", "$INSTALL_DIR")}
10011
10331
  rm -rf "$INSTALL_DIR"
10012
10332
  cp -R "$BUNDLE_DIR" "$INSTALL_DIR"
10013
10333
  ${renderInstallerUserConfigSnippet(config, "opencode", "$INSTALL_DIR")}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orchid-labs/pluxx",
3
- "version": "0.1.18",
3
+ "version": "0.1.20",
4
4
  "description": "Build AI agent plugins once. Prime-time on Claude Code, Cursor, Codex, and OpenCode, with beta generators for additional hosts.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",