@codyswann/lisa 2.335.0 → 2.336.1

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.
Files changed (75) hide show
  1. package/all/copy-overwrite/scripts/lisa-hooks/sonar-secrets.sh +25 -23
  2. package/dist/core/upstream-evidence-manifest.js +5 -5
  3. package/package.json +1 -1
  4. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  5. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  6. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/surfaces.mjs +42 -2
  7. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +1 -1
  8. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +33 -6
  9. package/plugins/lisa/hooks/sonar-secrets.sh +25 -23
  10. package/plugins/lisa/skills/lisa-secrets-access/scripts/surfaces.mjs +42 -2
  11. package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +1 -1
  12. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +33 -6
  13. package/plugins/lisa-agy/plugin.json +1 -1
  14. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/surfaces.mjs +42 -2
  15. package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +1 -1
  16. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +33 -6
  17. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-copilot/hooks/sonar-secrets.sh +25 -23
  24. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/surfaces.mjs +42 -2
  25. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +1 -1
  26. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +33 -6
  27. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-cursor/hooks/sonar-secrets.sh +25 -23
  29. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/surfaces.mjs +42 -2
  30. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +1 -1
  31. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +33 -6
  32. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo-agy/plugin.json +1 -1
  35. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  45. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  55. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails-agy/plugin.json +1 -1
  60. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  65. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  70. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  71. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  72. package/plugins/src/base/hooks/sonar-secrets.sh +25 -23
  73. package/plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs +42 -2
  74. package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +1 -1
  75. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +33 -6
@@ -39,7 +39,7 @@
39
39
  set -uo pipefail
40
40
 
41
41
  event="${1:-}"
42
- [ -n "$event" ] || exit 0
42
+ [[ -n "$event" ]] || exit 0
43
43
 
44
44
  # Nothing installed, nothing to enforce. Matches the vendor shim's own guard.
45
45
  command -v sonar >/dev/null 2>&1 || exit 0
@@ -48,7 +48,7 @@ command -v sonar >/dev/null 2>&1 || exit 0
48
48
  # deliberately no way to turn the *blocking* back on from the environment: the
49
49
  # decision that a missing token must not stop work belongs in this file, where it
50
50
  # is reviewable, not in whatever shell happened to launch the agent.
51
- [ "${LISA_SONAR_HOOK:-on}" = "off" ] && exit 0
51
+ [[ "${LISA_SONAR_HOOK:-on}" == "off" ]] && exit 0
52
52
 
53
53
  payload="$(cat)"
54
54
 
@@ -68,23 +68,25 @@ is_inactive() {
68
68
  }
69
69
 
70
70
  # Run the resolver with a ceiling, because the provider call crosses the network
71
- # and this sits in front of every prompt and every file read. `timeout` is not
72
- # present on a stock macOS, so this is the portable equivalent: start it, poll,
73
- # give up. Giving up is safe — the caller treats an empty value as "the provider
74
- # had nothing", which lands on the warn path rather than a block.
71
+ # and this sits in front of every prompt and every file read.
72
+ #
73
+ # The value never touches the filesystem. An earlier version captured it through
74
+ # `mktemp` and deleted the file afterwards, which is a race, not a cleanup: a
75
+ # kill between the write and the `rm` leaves the token readable on disk (CWE-922)
76
+ # — the precise failure the one-store rule in lisa-secrets-access exists to
77
+ # prevent, reintroduced by the code enforcing it. Process substitution gives a
78
+ # `/dev/fd` pipe instead, so there is no path on disk to leak and nothing to
79
+ # clean up on any exit path, signalled or not.
80
+ #
81
+ # `read -t` supplies the ceiling. `timeout` is absent on a stock macOS and
82
+ # `coproc` needs Bash 4 (the hooks run under /bin/bash 3.2 there), so this is the
83
+ # portable form. A timeout leaves `value` empty, which the caller treats as "the
84
+ # provider had nothing" — the warn path, never a block. `|| true` because `read`
85
+ # also reports failure at EOF on the resolver's deliberately unterminated output.
75
86
  resolve_secret() {
76
- local resolver="$1" name="$2" out pid waited=0
77
- out="$(mktemp)" || return 0
78
- node "$resolver" get "$name" >"$out" 2>/dev/null &
79
- pid=$!
80
- while kill -0 "$pid" 2>/dev/null; do
81
- [ "$waited" -ge 10 ] && { kill -9 "$pid" 2>/dev/null; break; }
82
- sleep 1
83
- waited=$((waited + 1))
84
- done
85
- wait "$pid" 2>/dev/null
86
- cat "$out"
87
- rm -f "$out"
87
+ local resolver="$1" name="$2" value=""
88
+ IFS= read -r -t 10 value < <(node "$resolver" get "$name" 2>/dev/null) || true
89
+ printf '%s' "$value"
88
90
  }
89
91
 
90
92
  out="$(run_scanner)"
@@ -103,21 +105,21 @@ if is_inactive "$out"; then
103
105
  "$repo_root/.codex/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
104
106
  "$repo_root/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
105
107
  "$repo_root/node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs"; do
106
- if [ -f "$candidate" ]; then
108
+ if [[ -f "$candidate" ]]; then
107
109
  resolver="$candidate"
108
110
  break
109
111
  fi
110
112
  done
111
113
 
112
- if [ -n "$resolver" ] && command -v node >/dev/null 2>&1; then
114
+ if [[ -n "$resolver" ]] && command -v node >/dev/null 2>&1; then
113
115
  token="$(resolve_secret "$resolver" SONARQUBE_CLI_TOKEN)"
114
- if [ -n "$token" ]; then
116
+ if [[ -n "$token" ]]; then
115
117
  # Exported into this process only. Writing it anywhere durable would create
116
118
  # a second live copy of a credential whose single store is the provider —
117
119
  # see the one-store rule in lisa-secrets-access.
118
120
  export SONARQUBE_CLI_TOKEN="$token"
119
121
  org="$(resolve_secret "$resolver" SONARQUBE_CLI_ORG)"
120
- [ -n "$org" ] && export SONARQUBE_CLI_ORG="$org"
122
+ [[ -n "$org" ]] && export SONARQUBE_CLI_ORG="$org"
121
123
  out="$(run_scanner)"
122
124
  fi
123
125
  unset token org
@@ -135,5 +137,5 @@ if is_inactive "$out"; then
135
137
  exit 0
136
138
  fi
137
139
 
138
- [ -n "$out" ] && printf '%s' "$out"
140
+ [[ -n "$out" ]] && printf '%s' "$out"
139
141
  exit 0
@@ -9,7 +9,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
9
9
  "all/copy-overwrite/scripts/lisa-hooks/block-no-verify.sh": "a6ed91576efebb6ba40cfa4e9d8a3e8566314909210ae8d5860be62a3feb4cff",
10
10
  "all/copy-overwrite/scripts/lisa-hooks/block-shell-json-parsing.sh": "3b5f074f3ab5708030af507eea962389f3b067c5dbdc76580bd9e44d3ac6c603",
11
11
  "all/copy-overwrite/scripts/lisa-hooks/parity-safety-net.sh": "062c69eea85157f1e941ec3626d8912aa9f182af6ccdbe19687588a6074db5ce",
12
- "all/copy-overwrite/scripts/lisa-hooks/sonar-secrets.sh": "7a408788975a6351d53e14aa32bb9a5d161f33df9dfea59ba32784ae33a62e67",
12
+ "all/copy-overwrite/scripts/lisa-hooks/sonar-secrets.sh": "945453fb275a58aa7185e150e6b05e5810739750928ab5bd6edfbf87d1c8aa0d",
13
13
  "all/copy-overwrite/scripts/lisa-work-item.mjs": "6a8be8577242588f14ab52b25b2a22b6def53aefa9513b4d3d7c49f4d8027079",
14
14
  "all/create-only/.claude/rules/PROJECT_RULES.md": "6e1c7923ad2a15356babc60c97e7f97be728d790af285b6a7cd7d1b4d39cd97f",
15
15
  "all/create-only/.lisaignore": "3c9827e7e98daa46510b5357cd6dd9406da1821e64cc46d2eea9f311cd6d06b9",
@@ -313,7 +313,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
313
313
  "plugins/src/base/hooks/parity-safety-net.sh": "062c69eea85157f1e941ec3626d8912aa9f182af6ccdbe19687588a6074db5ce",
314
314
  "plugins/src/base/hooks/setup-jira-cli.sh": "a675f6b17ce7f0d6b9fb7daeba6d2bc59bcb48ef79b034076ea73b2b1e72ee09",
315
315
  "plugins/src/base/hooks/shell-write-nudge.sh": "69839af423f8792b1e52c71097316c3264425553031627c0a2f9409a9d5becd0",
316
- "plugins/src/base/hooks/sonar-secrets.sh": "7a408788975a6351d53e14aa32bb9a5d161f33df9dfea59ba32784ae33a62e67",
316
+ "plugins/src/base/hooks/sonar-secrets.sh": "945453fb275a58aa7185e150e6b05e5810739750928ab5bd6edfbf87d1c8aa0d",
317
317
  "plugins/src/base/hooks/threshold-ratchet-compare.mjs": "4535a145c5c5cbee13bdf970bc91d390a264043604e607288cdd6bf28de56192",
318
318
  "plugins/src/base/hooks/threshold-ratchet-families.mjs": "db14245ed89b483912af287bdee917a7a921dc21bb29b49be37fc799c05c413f",
319
319
  "plugins/src/base/hooks/threshold-ratchet.mjs": "04f660131bf50864239eb09cbf4d9555e53921254f6ab45ff2b85c173e160901",
@@ -560,7 +560,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
560
560
  "plugins/src/base/skills/lisa-secrets-access/scripts/read-secret-note.mjs": "1471d2108058f3059e242f9c71208c99fdbc212bde32de34b798eaa9a27ffbcc",
561
561
  "plugins/src/base/skills/lisa-secrets-access/scripts/resolve-secret.mjs": "04ab89bc1f890526ba3b693cd3b022abc5a24e078b93c08fe7dfd586452b70b1",
562
562
  "plugins/src/base/skills/lisa-secrets-access/scripts/rotate-secret.mjs": "546dc1c2f279cee6db56c0f55c01aabae0c316842f32d3219c5af6d638936a9c",
563
- "plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs": "f8d4404ab105284ee072a26319366a71333c89abb14f9d71aafe461e7ea0686a",
563
+ "plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs": "21d3408de74f2691d61649862b086dfc37a5ce2553bcb17366fcd5d1c50a4ae8",
564
564
  "plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs": "62d1b87d1b852dc00c00ea9d132d9d6953f4ef77beb5fa4b93652c4424f33274",
565
565
  "plugins/src/base/skills/lisa-security-review/SKILL.md": "5a980eaf5efc4fcce66e75521ec5b1eda143a5b0d36e7157234a705dac94e3f5",
566
566
  "plugins/src/base/skills/lisa-security-zap-scan/SKILL.md": "a5189442dbf970cc49fd089ab03c386c8a205b856e09501f6ad1966321e7e4b5",
@@ -578,10 +578,10 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
578
578
  "plugins/src/base/skills/lisa-setup-local-env/scripts/local-env.mjs": "bff68aa66d6d4cd7cb9d0e39be691c6daa232edb097f8a601815432a32cca6a6",
579
579
  "plugins/src/base/skills/lisa-setup-notion/SKILL.md": "f5a1e9290789fd1c33675168d30461fb24a11c98a433ef777c1f536bc2f905ef",
580
580
  "plugins/src/base/skills/lisa-setup-remote-aws/SKILL.md": "80fbf157f9c562c033886c25a99b37356602edd9e61cd2d492f339769ddcf97e",
581
- "plugins/src/base/skills/lisa-setup-remote-env/SKILL.md": "b570ebc3faa5099dc8e43f33f5ba51bdc1e2312b3c49d28d04a3ae67f138041f",
581
+ "plugins/src/base/skills/lisa-setup-remote-env/SKILL.md": "b5993444364b361c7b365092ab424bc1d0eee61705a5a2471e4e1f443e30d78f",
582
582
  "plugins/src/base/skills/lisa-setup-remote-env/assets/session-start.sh": "cb63d08b14ab7aa2d405e6770e0cf7db5d6588ae1dcb924ea6224b026fcff496",
583
583
  "plugins/src/base/skills/lisa-setup-remote-env/assets/setup.sh": "c30d98a5645f033fc1d3a723043691b9ad997ae5666df539ff6aa19c563431b2",
584
- "plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs": "aa703a3fcadd847594c694f458fc41edd39590dfabeddf4fed1546b73099f33f",
584
+ "plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs": "830a2c75fabb0069ebe1bf3f5ec1c1f4dd3437fea6c059b41b8ab4092277025f",
585
585
  "plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs": "496de5aed034692fee421dc3a1fbad9f85ed9e1d0c83a1080f781a486d0274e6",
586
586
  "plugins/src/base/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs": "82bc2a27a0a5afb2ff413a88365c619fd7f0eaada5de3ccabf2000938f0607a4",
587
587
  "plugins/src/base/skills/lisa-setup-sonar/SKILL.md": "b18e07ae942f75aad5730c0535840ef5f7115072f1b6cc62afac83196b28d6a3",
package/package.json CHANGED
@@ -115,7 +115,7 @@
115
115
  "brace-expansion": ">=5.0.9"
116
116
  },
117
117
  "name": "@codyswann/lisa",
118
- "version": "2.335.0",
118
+ "version": "2.336.1",
119
119
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
120
120
  "main": "dist/index.js",
121
121
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.335.0",
3
+ "version": "2.336.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.335.0",
3
+ "version": "2.336.1",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -165,9 +165,19 @@ export function materializedPaths(namespace, env = process.env) {
165
165
  * @param {string} [cwd] Directory to look in.
166
166
  * @returns {object} Resolved configuration with defaults applied.
167
167
  */
168
- export function readConfig(cwd = process.cwd()) {
168
+ export function readConfig(cwd = process.cwd(), env = process.env) {
169
169
  const path = join(cwd, ".lisa.config.json");
170
- if (!existsSync(path)) return withSurface(DEFAULTS);
170
+ // No repository is a real, supported state — not just a missing file.
171
+ //
172
+ // A Claude Tag channel session runs as the organization with no user account,
173
+ // and a repository "doesn't enter a session until a request names it". So a
174
+ // session can legitimately have no checkout and therefore no config, while
175
+ // still needing the credentials and profiles every other surface gets.
176
+ //
177
+ // The only values a config would supply here are the namespace and provider,
178
+ // so they can come from the environment instead. Everything else keeps its
179
+ // default, and an environment that sets neither still gets the old behaviour.
180
+ if (!existsSync(path)) return withSurface(fromEnvironment(env));
171
181
  let cfg;
172
182
  try {
173
183
  cfg = JSON.parse(readFileSync(path, "utf8")).secrets;
@@ -186,6 +196,36 @@ export function readConfig(cwd = process.cwd()) {
186
196
  });
187
197
  }
188
198
 
199
+ /**
200
+ * Build a configuration from the environment, for sessions with no checkout.
201
+ *
202
+ * `LISA_SECRETS_NAMESPACE` is the one value with no safe default: it names the
203
+ * directory secrets are written to and the tenant they belong to, so guessing
204
+ * it would write one tenant's credentials under another's name. Absent, this
205
+ * returns the ordinary defaults and the caller behaves exactly as before.
206
+ *
207
+ * The bootstrap key follows the namespace by convention
208
+ * (`BWS_ACCESS_TOKEN_<namespace>`) so a channel environment needs one variable
209
+ * set, not two.
210
+ * @param {object} env The environment to read.
211
+ * @returns {object} A configuration, defaulted where the environment is silent.
212
+ */
213
+ function fromEnvironment(env) {
214
+ const namespace = (env.LISA_SECRETS_NAMESPACE ?? "").trim();
215
+ if (!namespace) return DEFAULTS;
216
+
217
+ const provider = (env.LISA_SECRETS_PROVIDER ?? "").trim() || "bitwarden";
218
+ return {
219
+ ...DEFAULTS,
220
+ provider,
221
+ namespace: assertNamespace(namespace),
222
+ bootstrap: {
223
+ ...DEFAULTS.bootstrap,
224
+ key: env.LISA_SECRETS_BOOTSTRAP_KEY ?? `BWS_ACCESS_TOKEN_${namespace}`,
225
+ },
226
+ };
227
+ }
228
+
189
229
  /**
190
230
  * Attach the resolved surface and its capabilities to a configuration.
191
231
  * @param {object} cfg Configuration without surface resolution.
@@ -13,7 +13,7 @@ Prepare a remote surface so a host project can execute there. Today that means *
13
13
  The remote environment's own configuration fields stay **one line into the repository**. Nothing else is pasted into a vendor UI.
14
14
 
15
15
  ```text
16
- setup: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; }; exit "$rc"
16
+ setup: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] && exit "$rc"; g=0; for c in ./.git ./*/.git "$HOME"/*/.git /workspace/*/.git; do [ -e "$c" ] && g=1; done; if [ "$g" -eq 1 ]; then echo "lisa-remote-env entrypoint not found, but a checkout is present. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; fi; if [ -n "${LISA_SECRETS_NAMESPACE:-}" ]; then echo "No checkout; preparing tools and credentials for $LISA_SECRETS_NAMESPACE."; npx -y @codyswann/lisa@latest workstation --provider "${LISA_SECRETS_PROVIDER:-bitwarden}" --yes || true; npx -y @codyswann/lisa@latest remote-env --phase=secrets || true; else echo "No checkout and no LISA_SECRETS_NAMESPACE; nothing to prepare."; fi; exit 0
17
17
  maintenance: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; }; exit "$rc"
18
18
  ```
19
19
 
@@ -559,9 +559,26 @@ export function installAssets(cwd = process.cwd()) {
559
559
  * whenever cwd happens to be one of the roots.
560
560
  *
561
561
  * The status is the first failure and every checkout is still attempted: one
562
- * broken repository must neither hide the others nor report success. The
563
- * explicit `exit 1` on no matches matters because a `for` loop over a glob that
564
- * matches nothing otherwise exits 0 the quiet success this guards against.
562
+ * broken repository must neither hide the others nor report success.
563
+ *
564
+ * Preparing nothing has two causes, and they are not the same failure:
565
+ *
566
+ * - A checkout is present but carries no entrypoint. That is a
567
+ * misconfiguration, and a `for` loop over a glob that matches nothing exits
568
+ * 0 on its own — the quiet success this has always guarded against. It
569
+ * still fails loudly, printing the layout it found rather than only the
570
+ * path it wanted.
571
+ * - There is NO checkout. A Claude Tag channel session runs as the
572
+ * organization with no user account, and a repository does not enter the
573
+ * session until a request names one, so this is an ordinary state rather
574
+ * than an error. Failing here would be severe: a setup script that exits
575
+ * non-zero stops the session from starting at all, so the old blanket
576
+ * `exit 1` would have killed every repo-less channel session.
577
+ *
578
+ * With no checkout, `LISA_SECRETS_NAMESPACE` decides whether there is anything
579
+ * to do: it names the tenant, which is the one value no default can supply.
580
+ * Given it, the machine itself is prepared — tools and credentials, no repo
581
+ * required. Without it there is nothing to prepare and nothing to fail about.
565
582
  *
566
583
  * Failure prints the layout it found rather than only the path it wanted. This
567
584
  * field lives in a vendor settings box with a slow edit-and-retry loop, and a
@@ -580,9 +597,19 @@ export const SETUP_FIELD =
580
597
  'do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); ' +
581
598
  'case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; ' +
582
599
  'n=$((n+1)); bash "$f" || rc=$?; done; ' +
583
- '[ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found. ' +
584
- 'PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; }; ' +
585
- 'exit "$rc"';
600
+ '[ "$n" -gt 0 ] && exit "$rc"; ' +
601
+ 'g=0; for c in ./.git ./*/.git "$HOME"/*/.git /workspace/*/.git; ' +
602
+ 'do [ -e "$c" ] && g=1; done; ' +
603
+ 'if [ "$g" -eq 1 ]; then ' +
604
+ 'echo "lisa-remote-env entrypoint not found, but a checkout is present. ' +
605
+ 'PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; fi; ' +
606
+ 'if [ -n "${LISA_SECRETS_NAMESPACE:-}" ]; then ' +
607
+ 'echo "No checkout; preparing tools and credentials for $LISA_SECRETS_NAMESPACE."; ' +
608
+ "npx -y @codyswann/lisa@latest workstation " +
609
+ '--provider "${LISA_SECRETS_PROVIDER:-bitwarden}" --yes || true; ' +
610
+ "npx -y @codyswann/lisa@latest remote-env --phase=secrets || true; " +
611
+ 'else echo "No checkout and no LISA_SECRETS_NAMESPACE; nothing to prepare."; fi; ' +
612
+ "exit 0";
586
613
 
587
614
  /**
588
615
  * Register the session-start hook at USER scope, so it fires wherever the
@@ -39,7 +39,7 @@
39
39
  set -uo pipefail
40
40
 
41
41
  event="${1:-}"
42
- [ -n "$event" ] || exit 0
42
+ [[ -n "$event" ]] || exit 0
43
43
 
44
44
  # Nothing installed, nothing to enforce. Matches the vendor shim's own guard.
45
45
  command -v sonar >/dev/null 2>&1 || exit 0
@@ -48,7 +48,7 @@ command -v sonar >/dev/null 2>&1 || exit 0
48
48
  # deliberately no way to turn the *blocking* back on from the environment: the
49
49
  # decision that a missing token must not stop work belongs in this file, where it
50
50
  # is reviewable, not in whatever shell happened to launch the agent.
51
- [ "${LISA_SONAR_HOOK:-on}" = "off" ] && exit 0
51
+ [[ "${LISA_SONAR_HOOK:-on}" == "off" ]] && exit 0
52
52
 
53
53
  payload="$(cat)"
54
54
 
@@ -68,23 +68,25 @@ is_inactive() {
68
68
  }
69
69
 
70
70
  # Run the resolver with a ceiling, because the provider call crosses the network
71
- # and this sits in front of every prompt and every file read. `timeout` is not
72
- # present on a stock macOS, so this is the portable equivalent: start it, poll,
73
- # give up. Giving up is safe — the caller treats an empty value as "the provider
74
- # had nothing", which lands on the warn path rather than a block.
71
+ # and this sits in front of every prompt and every file read.
72
+ #
73
+ # The value never touches the filesystem. An earlier version captured it through
74
+ # `mktemp` and deleted the file afterwards, which is a race, not a cleanup: a
75
+ # kill between the write and the `rm` leaves the token readable on disk (CWE-922)
76
+ # — the precise failure the one-store rule in lisa-secrets-access exists to
77
+ # prevent, reintroduced by the code enforcing it. Process substitution gives a
78
+ # `/dev/fd` pipe instead, so there is no path on disk to leak and nothing to
79
+ # clean up on any exit path, signalled or not.
80
+ #
81
+ # `read -t` supplies the ceiling. `timeout` is absent on a stock macOS and
82
+ # `coproc` needs Bash 4 (the hooks run under /bin/bash 3.2 there), so this is the
83
+ # portable form. A timeout leaves `value` empty, which the caller treats as "the
84
+ # provider had nothing" — the warn path, never a block. `|| true` because `read`
85
+ # also reports failure at EOF on the resolver's deliberately unterminated output.
75
86
  resolve_secret() {
76
- local resolver="$1" name="$2" out pid waited=0
77
- out="$(mktemp)" || return 0
78
- node "$resolver" get "$name" >"$out" 2>/dev/null &
79
- pid=$!
80
- while kill -0 "$pid" 2>/dev/null; do
81
- [ "$waited" -ge 10 ] && { kill -9 "$pid" 2>/dev/null; break; }
82
- sleep 1
83
- waited=$((waited + 1))
84
- done
85
- wait "$pid" 2>/dev/null
86
- cat "$out"
87
- rm -f "$out"
87
+ local resolver="$1" name="$2" value=""
88
+ IFS= read -r -t 10 value < <(node "$resolver" get "$name" 2>/dev/null) || true
89
+ printf '%s' "$value"
88
90
  }
89
91
 
90
92
  out="$(run_scanner)"
@@ -103,21 +105,21 @@ if is_inactive "$out"; then
103
105
  "$repo_root/.codex/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
104
106
  "$repo_root/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs" \
105
107
  "$repo_root/node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs"; do
106
- if [ -f "$candidate" ]; then
108
+ if [[ -f "$candidate" ]]; then
107
109
  resolver="$candidate"
108
110
  break
109
111
  fi
110
112
  done
111
113
 
112
- if [ -n "$resolver" ] && command -v node >/dev/null 2>&1; then
114
+ if [[ -n "$resolver" ]] && command -v node >/dev/null 2>&1; then
113
115
  token="$(resolve_secret "$resolver" SONARQUBE_CLI_TOKEN)"
114
- if [ -n "$token" ]; then
116
+ if [[ -n "$token" ]]; then
115
117
  # Exported into this process only. Writing it anywhere durable would create
116
118
  # a second live copy of a credential whose single store is the provider —
117
119
  # see the one-store rule in lisa-secrets-access.
118
120
  export SONARQUBE_CLI_TOKEN="$token"
119
121
  org="$(resolve_secret "$resolver" SONARQUBE_CLI_ORG)"
120
- [ -n "$org" ] && export SONARQUBE_CLI_ORG="$org"
122
+ [[ -n "$org" ]] && export SONARQUBE_CLI_ORG="$org"
121
123
  out="$(run_scanner)"
122
124
  fi
123
125
  unset token org
@@ -135,5 +137,5 @@ if is_inactive "$out"; then
135
137
  exit 0
136
138
  fi
137
139
 
138
- [ -n "$out" ] && printf '%s' "$out"
140
+ [[ -n "$out" ]] && printf '%s' "$out"
139
141
  exit 0
@@ -165,9 +165,19 @@ export function materializedPaths(namespace, env = process.env) {
165
165
  * @param {string} [cwd] Directory to look in.
166
166
  * @returns {object} Resolved configuration with defaults applied.
167
167
  */
168
- export function readConfig(cwd = process.cwd()) {
168
+ export function readConfig(cwd = process.cwd(), env = process.env) {
169
169
  const path = join(cwd, ".lisa.config.json");
170
- if (!existsSync(path)) return withSurface(DEFAULTS);
170
+ // No repository is a real, supported state — not just a missing file.
171
+ //
172
+ // A Claude Tag channel session runs as the organization with no user account,
173
+ // and a repository "doesn't enter a session until a request names it". So a
174
+ // session can legitimately have no checkout and therefore no config, while
175
+ // still needing the credentials and profiles every other surface gets.
176
+ //
177
+ // The only values a config would supply here are the namespace and provider,
178
+ // so they can come from the environment instead. Everything else keeps its
179
+ // default, and an environment that sets neither still gets the old behaviour.
180
+ if (!existsSync(path)) return withSurface(fromEnvironment(env));
171
181
  let cfg;
172
182
  try {
173
183
  cfg = JSON.parse(readFileSync(path, "utf8")).secrets;
@@ -186,6 +196,36 @@ export function readConfig(cwd = process.cwd()) {
186
196
  });
187
197
  }
188
198
 
199
+ /**
200
+ * Build a configuration from the environment, for sessions with no checkout.
201
+ *
202
+ * `LISA_SECRETS_NAMESPACE` is the one value with no safe default: it names the
203
+ * directory secrets are written to and the tenant they belong to, so guessing
204
+ * it would write one tenant's credentials under another's name. Absent, this
205
+ * returns the ordinary defaults and the caller behaves exactly as before.
206
+ *
207
+ * The bootstrap key follows the namespace by convention
208
+ * (`BWS_ACCESS_TOKEN_<namespace>`) so a channel environment needs one variable
209
+ * set, not two.
210
+ * @param {object} env The environment to read.
211
+ * @returns {object} A configuration, defaulted where the environment is silent.
212
+ */
213
+ function fromEnvironment(env) {
214
+ const namespace = (env.LISA_SECRETS_NAMESPACE ?? "").trim();
215
+ if (!namespace) return DEFAULTS;
216
+
217
+ const provider = (env.LISA_SECRETS_PROVIDER ?? "").trim() || "bitwarden";
218
+ return {
219
+ ...DEFAULTS,
220
+ provider,
221
+ namespace: assertNamespace(namespace),
222
+ bootstrap: {
223
+ ...DEFAULTS.bootstrap,
224
+ key: env.LISA_SECRETS_BOOTSTRAP_KEY ?? `BWS_ACCESS_TOKEN_${namespace}`,
225
+ },
226
+ };
227
+ }
228
+
189
229
  /**
190
230
  * Attach the resolved surface and its capabilities to a configuration.
191
231
  * @param {object} cfg Configuration without surface resolution.
@@ -13,7 +13,7 @@ Prepare a remote surface so a host project can execute there. Today that means *
13
13
  The remote environment's own configuration fields stay **one line into the repository**. Nothing else is pasted into a vendor UI.
14
14
 
15
15
  ```text
16
- setup: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; }; exit "$rc"
16
+ setup: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] && exit "$rc"; g=0; for c in ./.git ./*/.git "$HOME"/*/.git /workspace/*/.git; do [ -e "$c" ] && g=1; done; if [ "$g" -eq 1 ]; then echo "lisa-remote-env entrypoint not found, but a checkout is present. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; fi; if [ -n "${LISA_SECRETS_NAMESPACE:-}" ]; then echo "No checkout; preparing tools and credentials for $LISA_SECRETS_NAMESPACE."; npx -y @codyswann/lisa@latest workstation --provider "${LISA_SECRETS_PROVIDER:-bitwarden}" --yes || true; npx -y @codyswann/lisa@latest remote-env --phase=secrets || true; else echo "No checkout and no LISA_SECRETS_NAMESPACE; nothing to prepare."; fi; exit 0
17
17
  maintenance: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; }; exit "$rc"
18
18
  ```
19
19
 
@@ -559,9 +559,26 @@ export function installAssets(cwd = process.cwd()) {
559
559
  * whenever cwd happens to be one of the roots.
560
560
  *
561
561
  * The status is the first failure and every checkout is still attempted: one
562
- * broken repository must neither hide the others nor report success. The
563
- * explicit `exit 1` on no matches matters because a `for` loop over a glob that
564
- * matches nothing otherwise exits 0 the quiet success this guards against.
562
+ * broken repository must neither hide the others nor report success.
563
+ *
564
+ * Preparing nothing has two causes, and they are not the same failure:
565
+ *
566
+ * - A checkout is present but carries no entrypoint. That is a
567
+ * misconfiguration, and a `for` loop over a glob that matches nothing exits
568
+ * 0 on its own — the quiet success this has always guarded against. It
569
+ * still fails loudly, printing the layout it found rather than only the
570
+ * path it wanted.
571
+ * - There is NO checkout. A Claude Tag channel session runs as the
572
+ * organization with no user account, and a repository does not enter the
573
+ * session until a request names one, so this is an ordinary state rather
574
+ * than an error. Failing here would be severe: a setup script that exits
575
+ * non-zero stops the session from starting at all, so the old blanket
576
+ * `exit 1` would have killed every repo-less channel session.
577
+ *
578
+ * With no checkout, `LISA_SECRETS_NAMESPACE` decides whether there is anything
579
+ * to do: it names the tenant, which is the one value no default can supply.
580
+ * Given it, the machine itself is prepared — tools and credentials, no repo
581
+ * required. Without it there is nothing to prepare and nothing to fail about.
565
582
  *
566
583
  * Failure prints the layout it found rather than only the path it wanted. This
567
584
  * field lives in a vendor settings box with a slow edit-and-retry loop, and a
@@ -580,9 +597,19 @@ export const SETUP_FIELD =
580
597
  'do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); ' +
581
598
  'case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; ' +
582
599
  'n=$((n+1)); bash "$f" || rc=$?; done; ' +
583
- '[ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found. ' +
584
- 'PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; }; ' +
585
- 'exit "$rc"';
600
+ '[ "$n" -gt 0 ] && exit "$rc"; ' +
601
+ 'g=0; for c in ./.git ./*/.git "$HOME"/*/.git /workspace/*/.git; ' +
602
+ 'do [ -e "$c" ] && g=1; done; ' +
603
+ 'if [ "$g" -eq 1 ]; then ' +
604
+ 'echo "lisa-remote-env entrypoint not found, but a checkout is present. ' +
605
+ 'PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; fi; ' +
606
+ 'if [ -n "${LISA_SECRETS_NAMESPACE:-}" ]; then ' +
607
+ 'echo "No checkout; preparing tools and credentials for $LISA_SECRETS_NAMESPACE."; ' +
608
+ "npx -y @codyswann/lisa@latest workstation " +
609
+ '--provider "${LISA_SECRETS_PROVIDER:-bitwarden}" --yes || true; ' +
610
+ "npx -y @codyswann/lisa@latest remote-env --phase=secrets || true; " +
611
+ 'else echo "No checkout and no LISA_SECRETS_NAMESPACE; nothing to prepare."; fi; ' +
612
+ "exit 0";
586
613
 
587
614
  /**
588
615
  * Register the session-start hook at USER scope, so it fires wherever the
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.335.0",
3
+ "version": "2.336.1",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -165,9 +165,19 @@ export function materializedPaths(namespace, env = process.env) {
165
165
  * @param {string} [cwd] Directory to look in.
166
166
  * @returns {object} Resolved configuration with defaults applied.
167
167
  */
168
- export function readConfig(cwd = process.cwd()) {
168
+ export function readConfig(cwd = process.cwd(), env = process.env) {
169
169
  const path = join(cwd, ".lisa.config.json");
170
- if (!existsSync(path)) return withSurface(DEFAULTS);
170
+ // No repository is a real, supported state — not just a missing file.
171
+ //
172
+ // A Claude Tag channel session runs as the organization with no user account,
173
+ // and a repository "doesn't enter a session until a request names it". So a
174
+ // session can legitimately have no checkout and therefore no config, while
175
+ // still needing the credentials and profiles every other surface gets.
176
+ //
177
+ // The only values a config would supply here are the namespace and provider,
178
+ // so they can come from the environment instead. Everything else keeps its
179
+ // default, and an environment that sets neither still gets the old behaviour.
180
+ if (!existsSync(path)) return withSurface(fromEnvironment(env));
171
181
  let cfg;
172
182
  try {
173
183
  cfg = JSON.parse(readFileSync(path, "utf8")).secrets;
@@ -186,6 +196,36 @@ export function readConfig(cwd = process.cwd()) {
186
196
  });
187
197
  }
188
198
 
199
+ /**
200
+ * Build a configuration from the environment, for sessions with no checkout.
201
+ *
202
+ * `LISA_SECRETS_NAMESPACE` is the one value with no safe default: it names the
203
+ * directory secrets are written to and the tenant they belong to, so guessing
204
+ * it would write one tenant's credentials under another's name. Absent, this
205
+ * returns the ordinary defaults and the caller behaves exactly as before.
206
+ *
207
+ * The bootstrap key follows the namespace by convention
208
+ * (`BWS_ACCESS_TOKEN_<namespace>`) so a channel environment needs one variable
209
+ * set, not two.
210
+ * @param {object} env The environment to read.
211
+ * @returns {object} A configuration, defaulted where the environment is silent.
212
+ */
213
+ function fromEnvironment(env) {
214
+ const namespace = (env.LISA_SECRETS_NAMESPACE ?? "").trim();
215
+ if (!namespace) return DEFAULTS;
216
+
217
+ const provider = (env.LISA_SECRETS_PROVIDER ?? "").trim() || "bitwarden";
218
+ return {
219
+ ...DEFAULTS,
220
+ provider,
221
+ namespace: assertNamespace(namespace),
222
+ bootstrap: {
223
+ ...DEFAULTS.bootstrap,
224
+ key: env.LISA_SECRETS_BOOTSTRAP_KEY ?? `BWS_ACCESS_TOKEN_${namespace}`,
225
+ },
226
+ };
227
+ }
228
+
189
229
  /**
190
230
  * Attach the resolved surface and its capabilities to a configuration.
191
231
  * @param {object} cfg Configuration without surface resolution.
@@ -13,7 +13,7 @@ Prepare a remote surface so a host project can execute there. Today that means *
13
13
  The remote environment's own configuration fields stay **one line into the repository**. Nothing else is pasted into a vendor UI.
14
14
 
15
15
  ```text
16
- setup: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; }; exit "$rc"
16
+ setup: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] && exit "$rc"; g=0; for c in ./.git ./*/.git "$HOME"/*/.git /workspace/*/.git; do [ -e "$c" ] && g=1; done; if [ "$g" -eq 1 ]; then echo "lisa-remote-env entrypoint not found, but a checkout is present. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; fi; if [ -n "${LISA_SECRETS_NAMESPACE:-}" ]; then echo "No checkout; preparing tools and credentials for $LISA_SECRETS_NAMESPACE."; npx -y @codyswann/lisa@latest workstation --provider "${LISA_SECRETS_PROVIDER:-bitwarden}" --yes || true; npx -y @codyswann/lisa@latest remote-env --phase=secrets || true; else echo "No checkout and no LISA_SECRETS_NAMESPACE; nothing to prepare."; fi; exit 0
17
17
  maintenance: n=0; rc=0; seen=""; for f in scripts/lisa-remote-env/setup.sh */scripts/lisa-remote-env/setup.sh "$HOME"/scripts/lisa-remote-env/setup.sh "$HOME"/*/scripts/lisa-remote-env/setup.sh /workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; n=$((n+1)); bash "$f" || rc=$?; done; [ "$n" -gt 0 ] || { echo "lisa-remote-env entrypoint not found. PWD=$PWD HOME=$HOME" >&2; ls -1 . "$HOME" /workspace 2>&1 | head -40 >&2; exit 1; }; exit "$rc"
18
18
  ```
19
19