@codyswann/lisa 2.338.3 → 2.338.5
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/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +5 -3
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +23 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/surfaces.mjs +7 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +125 -9
- package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +23 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/surfaces.mjs +7 -1
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +125 -9
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +23 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/surfaces.mjs +7 -1
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +125 -9
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +23 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/surfaces.mjs +7 -1
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +125 -9
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +23 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/surfaces.mjs +7 -1
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +125 -9
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +23 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs +7 -1
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +125 -9
package/package.json
CHANGED
|
@@ -118,7 +118,7 @@
|
|
|
118
118
|
}
|
|
119
119
|
},
|
|
120
120
|
"name": "@codyswann/lisa",
|
|
121
|
-
"version": "2.338.
|
|
121
|
+
"version": "2.338.5",
|
|
122
122
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
123
123
|
"main": "dist/index.js",
|
|
124
124
|
"exports": {
|
|
@@ -51,6 +51,29 @@ const PROVIDER_BOOTSTRAP_ENV = {
|
|
|
51
51
|
doppler: "DOPPLER_TOKEN",
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* The tenant-scoped bootstrap variable a provider expects, by convention.
|
|
56
|
+
*
|
|
57
|
+
* Exists so the repo-less path cannot invent one. It used to compose
|
|
58
|
+
* `BWS_ACCESS_TOKEN_<namespace>` literally, ignoring the provider it had just
|
|
59
|
+
* resolved — so a Doppler tenant with no checkout was told to set a Bitwarden
|
|
60
|
+
* variable, and its CLI failed with "Missing access token". That is precisely
|
|
61
|
+
* the confusion the two questions above are separated to prevent, reintroduced
|
|
62
|
+
* on the one surface with no config file to correct it.
|
|
63
|
+
*
|
|
64
|
+
* `null` for a provider with no env-var bootstrap — 1Password, Vault and AWS
|
|
65
|
+
* authenticate by other means — because a name invented for them would be a
|
|
66
|
+
* variable nothing reads, which is worse than admitting there is none.
|
|
67
|
+
* `providerEnv` already treats an unmapped provider as "inject nothing".
|
|
68
|
+
* @param {string} provider Provider name.
|
|
69
|
+
* @param {string} namespace Tenant namespace.
|
|
70
|
+
* @returns {string|null} The variable to set, or null when the provider has none.
|
|
71
|
+
*/
|
|
72
|
+
export function bootstrapKeyFor(provider, namespace) {
|
|
73
|
+
const canonical = PROVIDER_BOOTSTRAP_ENV[provider];
|
|
74
|
+
return canonical ? `${canonical}_${namespace}` : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
/**
|
|
55
78
|
* Build the child environment for a provider CLI, injecting the bootstrap under
|
|
56
79
|
* the name that CLI actually reads.
|
|
@@ -17,6 +17,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
|
|
20
|
+
import { bootstrapKeyFor } from "./providers.mjs";
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* Capabilities, per surface.
|
|
22
24
|
*
|
|
@@ -237,10 +239,14 @@ function fromEnvironment(env) {
|
|
|
237
239
|
namespace: assertNamespace(namespace),
|
|
238
240
|
bootstrap: {
|
|
239
241
|
...DEFAULTS.bootstrap,
|
|
242
|
+
// Derived from the provider resolved just above, not from Bitwarden's
|
|
243
|
+
// name. Hardcoding it told a Doppler tenant to set BWS_ACCESS_TOKEN_<ns>,
|
|
244
|
+
// which its CLI has never heard of — on the one surface that has no
|
|
245
|
+
// config file to override the guess.
|
|
240
246
|
key:
|
|
241
247
|
env.LISA_BOOTSTRAP_KEY ??
|
|
242
248
|
env.LISA_SECRETS_BOOTSTRAP_KEY ??
|
|
243
|
-
|
|
249
|
+
bootstrapKeyFor(provider, namespace),
|
|
244
250
|
},
|
|
245
251
|
};
|
|
246
252
|
}
|
package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs
CHANGED
|
@@ -662,7 +662,14 @@ export const SETUP_FIELD =
|
|
|
662
662
|
"/workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; " +
|
|
663
663
|
'do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); ' +
|
|
664
664
|
'case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; ' +
|
|
665
|
-
|
|
665
|
+
// `rc` keeps the FIRST failure, which is what the contract below promises.
|
|
666
|
+
//
|
|
667
|
+
// A bare `rc=$?` keeps the last one, so with two broken checkouts the
|
|
668
|
+
// operator is shown the second and the first is lost — the precise hiding the
|
|
669
|
+
// documented behaviour exists to prevent. A success never overwrites, because
|
|
670
|
+
// `||` only fires on failure, so the bug was confined to the multiple-failure
|
|
671
|
+
// case that the rule was written for.
|
|
672
|
+
'n=$((n+1)); bash "$f" || { s=$?; [ "$rc" -ne 0 ] || rc=$s; }; done; ' +
|
|
666
673
|
'[ "$n" -gt 0 ] && exit "$rc"; ' +
|
|
667
674
|
'g=0; for c in ./.git ./*/.git "$HOME"/*/.git /workspace/*/.git; ' +
|
|
668
675
|
'do [ -e "$c" ] && g=1; done; ' +
|
|
@@ -878,6 +885,52 @@ export function pinEnvironment(environmentId, cwd = process.cwd()) {
|
|
|
878
885
|
return path;
|
|
879
886
|
}
|
|
880
887
|
|
|
888
|
+
/**
|
|
889
|
+
* The lines an operator puts above the field, for a session with no checkout.
|
|
890
|
+
*
|
|
891
|
+
* Emitted as shell that runs, not as a template with holes: every hole is a
|
|
892
|
+
* hand substitution in a settings box with no review, which is the failure this
|
|
893
|
+
* whole file exists to prevent. So a provider that cannot be bootstrapped by an
|
|
894
|
+
* environment variable gets a sentence explaining that, not an `export` of a
|
|
895
|
+
* placeholder — a line that would be a syntax error if pasted.
|
|
896
|
+
*
|
|
897
|
+
* `LISA_PROVIDER` is exported even though the field defaults it, because the
|
|
898
|
+
* field's default is Bitwarden. A Doppler tenant that omits it gets `bws`
|
|
899
|
+
* installed and its own CLI missing, having configured everything correctly.
|
|
900
|
+
* @param {object} options Resolved identity and display strings.
|
|
901
|
+
* @returns {string[]} Lines to include in the emitted guidance.
|
|
902
|
+
*/
|
|
903
|
+
function repoLessExports({ namespace, key, provider, bootstrapKey, tenant }) {
|
|
904
|
+
// Two different reasons the key can be missing, and telling them apart is the
|
|
905
|
+
// whole value of the message. "Nothing was named" is the operator's to fix by
|
|
906
|
+
// re-running; "this provider has no such variable" is not fixable at all and
|
|
907
|
+
// must not read as an omission.
|
|
908
|
+
if (!tenant) {
|
|
909
|
+
return [
|
|
910
|
+
" # Re-run with --tenant=<name> and these are filled in for you.",
|
|
911
|
+
` export LISA_TENANT=${namespace}`,
|
|
912
|
+
" export <bootstrap variable>='<read from your credential manager>'",
|
|
913
|
+
" export LISA_SECRETS_SURFACE=claude-web",
|
|
914
|
+
];
|
|
915
|
+
}
|
|
916
|
+
if (!bootstrapKey) {
|
|
917
|
+
return [
|
|
918
|
+
` # ${provider} has no environment-variable bootstrap, so there is`,
|
|
919
|
+
" # nothing to export for it. Authenticate it the way its own CLI",
|
|
920
|
+
" # expects, then set the three below.",
|
|
921
|
+
` export LISA_TENANT=${namespace}`,
|
|
922
|
+
` export LISA_PROVIDER=${provider}`,
|
|
923
|
+
" export LISA_SECRETS_SURFACE=claude-web",
|
|
924
|
+
];
|
|
925
|
+
}
|
|
926
|
+
return [
|
|
927
|
+
` export LISA_TENANT=${namespace}`,
|
|
928
|
+
` export ${key}='<the same value as above>'`,
|
|
929
|
+
...(provider ? [` export LISA_PROVIDER=${provider}`] : []),
|
|
930
|
+
" export LISA_SECRETS_SURFACE=claude-web",
|
|
931
|
+
];
|
|
932
|
+
}
|
|
933
|
+
|
|
881
934
|
/**
|
|
882
935
|
* Produce the configuration a human pastes to provision a Claude cloud surface.
|
|
883
936
|
*
|
|
@@ -886,11 +939,16 @@ export function pinEnvironment(environmentId, cwd = process.cwd()) {
|
|
|
886
939
|
* settings page, no direct URL, and no endpoint. Emit is not a degraded option
|
|
887
940
|
* here, it is the only one — so the read-back in `verify-remote-env.mjs` is
|
|
888
941
|
* what makes the result trustworthy, exactly as it would be at any other tier.
|
|
889
|
-
* @param {{bootstrapKey: string|null}} options Project details.
|
|
942
|
+
* @param {{bootstrapKey: string|null, tenant?: string|null, provider?: string}} options Project details.
|
|
890
943
|
* @returns {string} Text to show the operator.
|
|
891
944
|
*/
|
|
892
|
-
export function emitClaudeWeb({
|
|
893
|
-
|
|
945
|
+
export function emitClaudeWeb({
|
|
946
|
+
bootstrapKey,
|
|
947
|
+
tenant = null,
|
|
948
|
+
provider = null,
|
|
949
|
+
}) {
|
|
950
|
+
const key = bootstrapKey ?? "<not resolved>";
|
|
951
|
+
const namespace = tenant ?? "<your namespace>";
|
|
894
952
|
return [
|
|
895
953
|
"Provisioning tier: EMIT — and for this surface that is the only tier.",
|
|
896
954
|
" A Claude cloud environment is account-scoped configuration edited in the",
|
|
@@ -917,11 +975,9 @@ export function emitClaudeWeb({ bootstrapKey }) {
|
|
|
917
975
|
" while the image is being built, before the environment exists. So a",
|
|
918
976
|
" session with no checkout has to be told its tenant inside the script",
|
|
919
977
|
" itself, or the field finds nothing to prepare and exits 0 looking like",
|
|
920
|
-
" success. Put
|
|
978
|
+
" success. Put the lines below FIRST, then the field after them:",
|
|
921
979
|
"",
|
|
922
|
-
|
|
923
|
-
` export ${key}='<the same value as above>'`,
|
|
924
|
-
" export LISA_SECRETS_SURFACE=claude-web",
|
|
980
|
+
...repoLessExports({ namespace, key, provider, bootstrapKey, tenant }),
|
|
925
981
|
"",
|
|
926
982
|
" LISA_SECRETS_SURFACE is set explicitly rather than left to detection:",
|
|
927
983
|
" detection keys off CLAUDE_CODE_REMOTE, and a surface that does not set",
|
|
@@ -1089,6 +1145,66 @@ function readBootstrapKey(cwd = process.cwd()) {
|
|
|
1089
1145
|
return JSON.parse(readFileSync(path, "utf8")).secrets?.bootstrap?.key ?? null;
|
|
1090
1146
|
}
|
|
1091
1147
|
|
|
1148
|
+
/**
|
|
1149
|
+
* Work out what to tell the operator to export, for a surface that has no repo.
|
|
1150
|
+
*
|
|
1151
|
+
* Emitting used to read the key out of `.lisa.config.json` in the working
|
|
1152
|
+
* directory, which made the command that configures a REPO-LESS environment
|
|
1153
|
+
* require a repository — so it was run from a checkout that happened to be
|
|
1154
|
+
* nearby, or it printed a placeholder the operator substituted by hand.
|
|
1155
|
+
*
|
|
1156
|
+
* Explicit flags win, then the environment, then a checkout if the caller
|
|
1157
|
+
* happens to be standing in one, then the convention. The provider matters
|
|
1158
|
+
* because it decides the variable's name: `bws` reads `BWS_ACCESS_TOKEN` and
|
|
1159
|
+
* `doppler` reads `DOPPLER_TOKEN`, so a tenant on one told to export the
|
|
1160
|
+
* other's name gets "Missing access token" from a CLI it did configure.
|
|
1161
|
+
* @param {string[]} argv Process arguments.
|
|
1162
|
+
* @param {Record<string, string|undefined>} [env] Environment to read.
|
|
1163
|
+
* @param {string} [cwd] Directory to look for a config in.
|
|
1164
|
+
* @returns {Promise<{tenant: string|null, provider: string, bootstrapKey: string|null}>} Resolved identity.
|
|
1165
|
+
*/
|
|
1166
|
+
export async function resolveEmitTarget(
|
|
1167
|
+
argv,
|
|
1168
|
+
env = process.env,
|
|
1169
|
+
cwd = process.cwd()
|
|
1170
|
+
) {
|
|
1171
|
+
const flag = name =>
|
|
1172
|
+
argv.find(a => a.startsWith(`--${name}=`))?.slice(name.length + 3) ?? null;
|
|
1173
|
+
|
|
1174
|
+
const tenant =
|
|
1175
|
+
flag("tenant") ||
|
|
1176
|
+
(env.LISA_TENANT ?? env.LISA_SECRETS_NAMESPACE ?? "").trim() ||
|
|
1177
|
+
null;
|
|
1178
|
+
const provider =
|
|
1179
|
+
flag("provider") ||
|
|
1180
|
+
(env.LISA_PROVIDER ?? env.LISA_SECRETS_PROVIDER ?? "").trim() ||
|
|
1181
|
+
"bitwarden";
|
|
1182
|
+
|
|
1183
|
+
// Validated before it can reach the output, by the same rule the runtime path
|
|
1184
|
+
// applies. This one becomes `export LISA_TENANT=<value>` in a block an
|
|
1185
|
+
// operator pastes verbatim, so `my tenant` or `has$var` would emit shell that
|
|
1186
|
+
// breaks — or worse, expands — where nothing reviews it. Refusing here costs
|
|
1187
|
+
// a re-run; emitting it costs a container nobody can explain.
|
|
1188
|
+
if (tenant) {
|
|
1189
|
+
const { assertNamespace } = await import(
|
|
1190
|
+
pathToFileURL(siblingScript("lisa-secrets-access", "surfaces.mjs")).href
|
|
1191
|
+
);
|
|
1192
|
+
assertNamespace(tenant);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// A configured key outranks the convention: a project may legitimately use a
|
|
1196
|
+
// name that is not derivable, and this command must not tell its operator to
|
|
1197
|
+
// set a different one from the one their sessions actually read.
|
|
1198
|
+
const configured = readBootstrapKey(cwd);
|
|
1199
|
+
if (configured) return { tenant, provider, bootstrapKey: configured };
|
|
1200
|
+
if (!tenant) return { tenant, provider, bootstrapKey: null };
|
|
1201
|
+
|
|
1202
|
+
const { bootstrapKeyFor } = await import(
|
|
1203
|
+
pathToFileURL(siblingScript("lisa-secrets-access", "providers.mjs")).href
|
|
1204
|
+
);
|
|
1205
|
+
return { tenant, provider, bootstrapKey: bootstrapKeyFor(provider, tenant) };
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1092
1208
|
async function main() {
|
|
1093
1209
|
// `--print-tools` answers "which CLIs does this secret set imply", reading
|
|
1094
1210
|
// the notes materialized alongside the values.
|
|
@@ -1163,7 +1279,7 @@ async function main() {
|
|
|
1163
1279
|
`Emitting is implemented for claude-web, which has no other tier.`
|
|
1164
1280
|
);
|
|
1165
1281
|
}
|
|
1166
|
-
console.log(emitClaudeWeb(
|
|
1282
|
+
console.log(emitClaudeWeb(await resolveEmitTarget(process.argv)));
|
|
1167
1283
|
return;
|
|
1168
1284
|
}
|
|
1169
1285
|
|
|
@@ -51,6 +51,29 @@ const PROVIDER_BOOTSTRAP_ENV = {
|
|
|
51
51
|
doppler: "DOPPLER_TOKEN",
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* The tenant-scoped bootstrap variable a provider expects, by convention.
|
|
56
|
+
*
|
|
57
|
+
* Exists so the repo-less path cannot invent one. It used to compose
|
|
58
|
+
* `BWS_ACCESS_TOKEN_<namespace>` literally, ignoring the provider it had just
|
|
59
|
+
* resolved — so a Doppler tenant with no checkout was told to set a Bitwarden
|
|
60
|
+
* variable, and its CLI failed with "Missing access token". That is precisely
|
|
61
|
+
* the confusion the two questions above are separated to prevent, reintroduced
|
|
62
|
+
* on the one surface with no config file to correct it.
|
|
63
|
+
*
|
|
64
|
+
* `null` for a provider with no env-var bootstrap — 1Password, Vault and AWS
|
|
65
|
+
* authenticate by other means — because a name invented for them would be a
|
|
66
|
+
* variable nothing reads, which is worse than admitting there is none.
|
|
67
|
+
* `providerEnv` already treats an unmapped provider as "inject nothing".
|
|
68
|
+
* @param {string} provider Provider name.
|
|
69
|
+
* @param {string} namespace Tenant namespace.
|
|
70
|
+
* @returns {string|null} The variable to set, or null when the provider has none.
|
|
71
|
+
*/
|
|
72
|
+
export function bootstrapKeyFor(provider, namespace) {
|
|
73
|
+
const canonical = PROVIDER_BOOTSTRAP_ENV[provider];
|
|
74
|
+
return canonical ? `${canonical}_${namespace}` : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
/**
|
|
55
78
|
* Build the child environment for a provider CLI, injecting the bootstrap under
|
|
56
79
|
* the name that CLI actually reads.
|
|
@@ -17,6 +17,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
|
|
20
|
+
import { bootstrapKeyFor } from "./providers.mjs";
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* Capabilities, per surface.
|
|
22
24
|
*
|
|
@@ -237,10 +239,14 @@ function fromEnvironment(env) {
|
|
|
237
239
|
namespace: assertNamespace(namespace),
|
|
238
240
|
bootstrap: {
|
|
239
241
|
...DEFAULTS.bootstrap,
|
|
242
|
+
// Derived from the provider resolved just above, not from Bitwarden's
|
|
243
|
+
// name. Hardcoding it told a Doppler tenant to set BWS_ACCESS_TOKEN_<ns>,
|
|
244
|
+
// which its CLI has never heard of — on the one surface that has no
|
|
245
|
+
// config file to override the guess.
|
|
240
246
|
key:
|
|
241
247
|
env.LISA_BOOTSTRAP_KEY ??
|
|
242
248
|
env.LISA_SECRETS_BOOTSTRAP_KEY ??
|
|
243
|
-
|
|
249
|
+
bootstrapKeyFor(provider, namespace),
|
|
244
250
|
},
|
|
245
251
|
};
|
|
246
252
|
}
|
|
@@ -662,7 +662,14 @@ export const SETUP_FIELD =
|
|
|
662
662
|
"/workspace/scripts/lisa-remote-env/setup.sh /workspace/*/scripts/lisa-remote-env/setup.sh; " +
|
|
663
663
|
'do [ -f "$f" ] || continue; d=$(cd "$(dirname "$f")" && pwd -P); ' +
|
|
664
664
|
'case " $seen " in *" $d "*) continue;; esac; seen="$seen $d"; ' +
|
|
665
|
-
|
|
665
|
+
// `rc` keeps the FIRST failure, which is what the contract below promises.
|
|
666
|
+
//
|
|
667
|
+
// A bare `rc=$?` keeps the last one, so with two broken checkouts the
|
|
668
|
+
// operator is shown the second and the first is lost — the precise hiding the
|
|
669
|
+
// documented behaviour exists to prevent. A success never overwrites, because
|
|
670
|
+
// `||` only fires on failure, so the bug was confined to the multiple-failure
|
|
671
|
+
// case that the rule was written for.
|
|
672
|
+
'n=$((n+1)); bash "$f" || { s=$?; [ "$rc" -ne 0 ] || rc=$s; }; done; ' +
|
|
666
673
|
'[ "$n" -gt 0 ] && exit "$rc"; ' +
|
|
667
674
|
'g=0; for c in ./.git ./*/.git "$HOME"/*/.git /workspace/*/.git; ' +
|
|
668
675
|
'do [ -e "$c" ] && g=1; done; ' +
|
|
@@ -878,6 +885,52 @@ export function pinEnvironment(environmentId, cwd = process.cwd()) {
|
|
|
878
885
|
return path;
|
|
879
886
|
}
|
|
880
887
|
|
|
888
|
+
/**
|
|
889
|
+
* The lines an operator puts above the field, for a session with no checkout.
|
|
890
|
+
*
|
|
891
|
+
* Emitted as shell that runs, not as a template with holes: every hole is a
|
|
892
|
+
* hand substitution in a settings box with no review, which is the failure this
|
|
893
|
+
* whole file exists to prevent. So a provider that cannot be bootstrapped by an
|
|
894
|
+
* environment variable gets a sentence explaining that, not an `export` of a
|
|
895
|
+
* placeholder — a line that would be a syntax error if pasted.
|
|
896
|
+
*
|
|
897
|
+
* `LISA_PROVIDER` is exported even though the field defaults it, because the
|
|
898
|
+
* field's default is Bitwarden. A Doppler tenant that omits it gets `bws`
|
|
899
|
+
* installed and its own CLI missing, having configured everything correctly.
|
|
900
|
+
* @param {object} options Resolved identity and display strings.
|
|
901
|
+
* @returns {string[]} Lines to include in the emitted guidance.
|
|
902
|
+
*/
|
|
903
|
+
function repoLessExports({ namespace, key, provider, bootstrapKey, tenant }) {
|
|
904
|
+
// Two different reasons the key can be missing, and telling them apart is the
|
|
905
|
+
// whole value of the message. "Nothing was named" is the operator's to fix by
|
|
906
|
+
// re-running; "this provider has no such variable" is not fixable at all and
|
|
907
|
+
// must not read as an omission.
|
|
908
|
+
if (!tenant) {
|
|
909
|
+
return [
|
|
910
|
+
" # Re-run with --tenant=<name> and these are filled in for you.",
|
|
911
|
+
` export LISA_TENANT=${namespace}`,
|
|
912
|
+
" export <bootstrap variable>='<read from your credential manager>'",
|
|
913
|
+
" export LISA_SECRETS_SURFACE=claude-web",
|
|
914
|
+
];
|
|
915
|
+
}
|
|
916
|
+
if (!bootstrapKey) {
|
|
917
|
+
return [
|
|
918
|
+
` # ${provider} has no environment-variable bootstrap, so there is`,
|
|
919
|
+
" # nothing to export for it. Authenticate it the way its own CLI",
|
|
920
|
+
" # expects, then set the three below.",
|
|
921
|
+
` export LISA_TENANT=${namespace}`,
|
|
922
|
+
` export LISA_PROVIDER=${provider}`,
|
|
923
|
+
" export LISA_SECRETS_SURFACE=claude-web",
|
|
924
|
+
];
|
|
925
|
+
}
|
|
926
|
+
return [
|
|
927
|
+
` export LISA_TENANT=${namespace}`,
|
|
928
|
+
` export ${key}='<the same value as above>'`,
|
|
929
|
+
...(provider ? [` export LISA_PROVIDER=${provider}`] : []),
|
|
930
|
+
" export LISA_SECRETS_SURFACE=claude-web",
|
|
931
|
+
];
|
|
932
|
+
}
|
|
933
|
+
|
|
881
934
|
/**
|
|
882
935
|
* Produce the configuration a human pastes to provision a Claude cloud surface.
|
|
883
936
|
*
|
|
@@ -886,11 +939,16 @@ export function pinEnvironment(environmentId, cwd = process.cwd()) {
|
|
|
886
939
|
* settings page, no direct URL, and no endpoint. Emit is not a degraded option
|
|
887
940
|
* here, it is the only one — so the read-back in `verify-remote-env.mjs` is
|
|
888
941
|
* what makes the result trustworthy, exactly as it would be at any other tier.
|
|
889
|
-
* @param {{bootstrapKey: string|null}} options Project details.
|
|
942
|
+
* @param {{bootstrapKey: string|null, tenant?: string|null, provider?: string}} options Project details.
|
|
890
943
|
* @returns {string} Text to show the operator.
|
|
891
944
|
*/
|
|
892
|
-
export function emitClaudeWeb({
|
|
893
|
-
|
|
945
|
+
export function emitClaudeWeb({
|
|
946
|
+
bootstrapKey,
|
|
947
|
+
tenant = null,
|
|
948
|
+
provider = null,
|
|
949
|
+
}) {
|
|
950
|
+
const key = bootstrapKey ?? "<not resolved>";
|
|
951
|
+
const namespace = tenant ?? "<your namespace>";
|
|
894
952
|
return [
|
|
895
953
|
"Provisioning tier: EMIT — and for this surface that is the only tier.",
|
|
896
954
|
" A Claude cloud environment is account-scoped configuration edited in the",
|
|
@@ -917,11 +975,9 @@ export function emitClaudeWeb({ bootstrapKey }) {
|
|
|
917
975
|
" while the image is being built, before the environment exists. So a",
|
|
918
976
|
" session with no checkout has to be told its tenant inside the script",
|
|
919
977
|
" itself, or the field finds nothing to prepare and exits 0 looking like",
|
|
920
|
-
" success. Put
|
|
978
|
+
" success. Put the lines below FIRST, then the field after them:",
|
|
921
979
|
"",
|
|
922
|
-
|
|
923
|
-
` export ${key}='<the same value as above>'`,
|
|
924
|
-
" export LISA_SECRETS_SURFACE=claude-web",
|
|
980
|
+
...repoLessExports({ namespace, key, provider, bootstrapKey, tenant }),
|
|
925
981
|
"",
|
|
926
982
|
" LISA_SECRETS_SURFACE is set explicitly rather than left to detection:",
|
|
927
983
|
" detection keys off CLAUDE_CODE_REMOTE, and a surface that does not set",
|
|
@@ -1089,6 +1145,66 @@ function readBootstrapKey(cwd = process.cwd()) {
|
|
|
1089
1145
|
return JSON.parse(readFileSync(path, "utf8")).secrets?.bootstrap?.key ?? null;
|
|
1090
1146
|
}
|
|
1091
1147
|
|
|
1148
|
+
/**
|
|
1149
|
+
* Work out what to tell the operator to export, for a surface that has no repo.
|
|
1150
|
+
*
|
|
1151
|
+
* Emitting used to read the key out of `.lisa.config.json` in the working
|
|
1152
|
+
* directory, which made the command that configures a REPO-LESS environment
|
|
1153
|
+
* require a repository — so it was run from a checkout that happened to be
|
|
1154
|
+
* nearby, or it printed a placeholder the operator substituted by hand.
|
|
1155
|
+
*
|
|
1156
|
+
* Explicit flags win, then the environment, then a checkout if the caller
|
|
1157
|
+
* happens to be standing in one, then the convention. The provider matters
|
|
1158
|
+
* because it decides the variable's name: `bws` reads `BWS_ACCESS_TOKEN` and
|
|
1159
|
+
* `doppler` reads `DOPPLER_TOKEN`, so a tenant on one told to export the
|
|
1160
|
+
* other's name gets "Missing access token" from a CLI it did configure.
|
|
1161
|
+
* @param {string[]} argv Process arguments.
|
|
1162
|
+
* @param {Record<string, string|undefined>} [env] Environment to read.
|
|
1163
|
+
* @param {string} [cwd] Directory to look for a config in.
|
|
1164
|
+
* @returns {Promise<{tenant: string|null, provider: string, bootstrapKey: string|null}>} Resolved identity.
|
|
1165
|
+
*/
|
|
1166
|
+
export async function resolveEmitTarget(
|
|
1167
|
+
argv,
|
|
1168
|
+
env = process.env,
|
|
1169
|
+
cwd = process.cwd()
|
|
1170
|
+
) {
|
|
1171
|
+
const flag = name =>
|
|
1172
|
+
argv.find(a => a.startsWith(`--${name}=`))?.slice(name.length + 3) ?? null;
|
|
1173
|
+
|
|
1174
|
+
const tenant =
|
|
1175
|
+
flag("tenant") ||
|
|
1176
|
+
(env.LISA_TENANT ?? env.LISA_SECRETS_NAMESPACE ?? "").trim() ||
|
|
1177
|
+
null;
|
|
1178
|
+
const provider =
|
|
1179
|
+
flag("provider") ||
|
|
1180
|
+
(env.LISA_PROVIDER ?? env.LISA_SECRETS_PROVIDER ?? "").trim() ||
|
|
1181
|
+
"bitwarden";
|
|
1182
|
+
|
|
1183
|
+
// Validated before it can reach the output, by the same rule the runtime path
|
|
1184
|
+
// applies. This one becomes `export LISA_TENANT=<value>` in a block an
|
|
1185
|
+
// operator pastes verbatim, so `my tenant` or `has$var` would emit shell that
|
|
1186
|
+
// breaks — or worse, expands — where nothing reviews it. Refusing here costs
|
|
1187
|
+
// a re-run; emitting it costs a container nobody can explain.
|
|
1188
|
+
if (tenant) {
|
|
1189
|
+
const { assertNamespace } = await import(
|
|
1190
|
+
pathToFileURL(siblingScript("lisa-secrets-access", "surfaces.mjs")).href
|
|
1191
|
+
);
|
|
1192
|
+
assertNamespace(tenant);
|
|
1193
|
+
}
|
|
1194
|
+
|
|
1195
|
+
// A configured key outranks the convention: a project may legitimately use a
|
|
1196
|
+
// name that is not derivable, and this command must not tell its operator to
|
|
1197
|
+
// set a different one from the one their sessions actually read.
|
|
1198
|
+
const configured = readBootstrapKey(cwd);
|
|
1199
|
+
if (configured) return { tenant, provider, bootstrapKey: configured };
|
|
1200
|
+
if (!tenant) return { tenant, provider, bootstrapKey: null };
|
|
1201
|
+
|
|
1202
|
+
const { bootstrapKeyFor } = await import(
|
|
1203
|
+
pathToFileURL(siblingScript("lisa-secrets-access", "providers.mjs")).href
|
|
1204
|
+
);
|
|
1205
|
+
return { tenant, provider, bootstrapKey: bootstrapKeyFor(provider, tenant) };
|
|
1206
|
+
}
|
|
1207
|
+
|
|
1092
1208
|
async function main() {
|
|
1093
1209
|
// `--print-tools` answers "which CLIs does this secret set imply", reading
|
|
1094
1210
|
// the notes materialized alongside the values.
|
|
@@ -1163,7 +1279,7 @@ async function main() {
|
|
|
1163
1279
|
`Emitting is implemented for claude-web, which has no other tier.`
|
|
1164
1280
|
);
|
|
1165
1281
|
}
|
|
1166
|
-
console.log(emitClaudeWeb(
|
|
1282
|
+
console.log(emitClaudeWeb(await resolveEmitTarget(process.argv)));
|
|
1167
1283
|
return;
|
|
1168
1284
|
}
|
|
1169
1285
|
|
|
@@ -51,6 +51,29 @@ const PROVIDER_BOOTSTRAP_ENV = {
|
|
|
51
51
|
doppler: "DOPPLER_TOKEN",
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
+
/**
|
|
55
|
+
* The tenant-scoped bootstrap variable a provider expects, by convention.
|
|
56
|
+
*
|
|
57
|
+
* Exists so the repo-less path cannot invent one. It used to compose
|
|
58
|
+
* `BWS_ACCESS_TOKEN_<namespace>` literally, ignoring the provider it had just
|
|
59
|
+
* resolved — so a Doppler tenant with no checkout was told to set a Bitwarden
|
|
60
|
+
* variable, and its CLI failed with "Missing access token". That is precisely
|
|
61
|
+
* the confusion the two questions above are separated to prevent, reintroduced
|
|
62
|
+
* on the one surface with no config file to correct it.
|
|
63
|
+
*
|
|
64
|
+
* `null` for a provider with no env-var bootstrap — 1Password, Vault and AWS
|
|
65
|
+
* authenticate by other means — because a name invented for them would be a
|
|
66
|
+
* variable nothing reads, which is worse than admitting there is none.
|
|
67
|
+
* `providerEnv` already treats an unmapped provider as "inject nothing".
|
|
68
|
+
* @param {string} provider Provider name.
|
|
69
|
+
* @param {string} namespace Tenant namespace.
|
|
70
|
+
* @returns {string|null} The variable to set, or null when the provider has none.
|
|
71
|
+
*/
|
|
72
|
+
export function bootstrapKeyFor(provider, namespace) {
|
|
73
|
+
const canonical = PROVIDER_BOOTSTRAP_ENV[provider];
|
|
74
|
+
return canonical ? `${canonical}_${namespace}` : null;
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
/**
|
|
55
78
|
* Build the child environment for a provider CLI, injecting the bootstrap under
|
|
56
79
|
* the name that CLI actually reads.
|
|
@@ -17,6 +17,8 @@ import { existsSync, readFileSync } from "node:fs";
|
|
|
17
17
|
import { homedir } from "node:os";
|
|
18
18
|
import { join } from "node:path";
|
|
19
19
|
|
|
20
|
+
import { bootstrapKeyFor } from "./providers.mjs";
|
|
21
|
+
|
|
20
22
|
/**
|
|
21
23
|
* Capabilities, per surface.
|
|
22
24
|
*
|
|
@@ -237,10 +239,14 @@ function fromEnvironment(env) {
|
|
|
237
239
|
namespace: assertNamespace(namespace),
|
|
238
240
|
bootstrap: {
|
|
239
241
|
...DEFAULTS.bootstrap,
|
|
242
|
+
// Derived from the provider resolved just above, not from Bitwarden's
|
|
243
|
+
// name. Hardcoding it told a Doppler tenant to set BWS_ACCESS_TOKEN_<ns>,
|
|
244
|
+
// which its CLI has never heard of — on the one surface that has no
|
|
245
|
+
// config file to override the guess.
|
|
240
246
|
key:
|
|
241
247
|
env.LISA_BOOTSTRAP_KEY ??
|
|
242
248
|
env.LISA_SECRETS_BOOTSTRAP_KEY ??
|
|
243
|
-
|
|
249
|
+
bootstrapKeyFor(provider, namespace),
|
|
244
250
|
},
|
|
245
251
|
};
|
|
246
252
|
}
|