@codyswann/lisa 2.316.2 → 2.317.0
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 +139 -9
- 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-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/agents/openai.yaml +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +137 -51
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/agents/openai.yaml +4 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- package/plugins/lisa/commands/implement.md +5 -3
- package/plugins/lisa/commands/setup/remote-env.md +7 -0
- package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa/skills/lisa-remote-dispatch/agents/openai.yaml +4 -0
- package/plugins/lisa/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/lisa/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/agents/openai.yaml +4 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- package/plugins/lisa-agy/commands/lisa/implement.md +5 -3
- package/plugins/lisa-agy/commands/lisa/setup/remote-env.md +7 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa-agy/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa-agy/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa-agy/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- 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/commands/lisa/implement.md +5 -3
- package/plugins/lisa-copilot/commands/lisa/setup/remote-env.md +7 -0
- package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa-copilot/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa-copilot/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa-copilot/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/commands/lisa/implement.md +5 -3
- package/plugins/lisa-cursor/commands/lisa/setup/remote-env.md +7 -0
- package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/lisa-cursor/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/lisa-cursor/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/lisa-cursor/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
- 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/commands/implement.md +5 -3
- package/plugins/src/base/commands/setup/remote-env.md +7 -0
- package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +17 -0
- package/plugins/src/base/skills/lisa-automation-status/SKILL.md +23 -0
- package/plugins/src/base/skills/lisa-doctor/SKILL.md +33 -0
- package/plugins/src/base/skills/lisa-implement/SKILL.md +17 -0
- package/plugins/src/base/skills/lisa-notion-access/SKILL.md +17 -0
- package/plugins/src/base/skills/lisa-remote-dispatch/SKILL.md +99 -0
- package/plugins/src/base/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
- package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +138 -52
- package/plugins/src/base/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
- package/plugins/src/base/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
- package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
- package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +69 -0
- package/plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +150 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
- package/plugins/src/base/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Print one credential's usage note without opening the values file.
|
|
4
|
+
*
|
|
5
|
+
* This is a separate program from the resolver on purpose. An agent about to
|
|
6
|
+
* use a credential needs to know its blast radius — scope, owner, rotation,
|
|
7
|
+
* cautions — and should be able to learn that without any code path that could
|
|
8
|
+
* serialize a secret value. Values never enter this process, so its output
|
|
9
|
+
* cannot leak one even if it is logged.
|
|
10
|
+
*
|
|
11
|
+
* Usage:
|
|
12
|
+
* read-secret-note.mjs NAME
|
|
13
|
+
* @module read-secret-note
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { existsSync, readFileSync } from "node:fs";
|
|
17
|
+
|
|
18
|
+
import { materializedPaths, readConfig } from "./surfaces.mjs";
|
|
19
|
+
|
|
20
|
+
/** A key must be an exact environment-variable name; see the naming rule. */
|
|
21
|
+
const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Load the notes manifest, rejecting a shape this version does not understand.
|
|
25
|
+
* @param {string} notesFile Path to `secret-notes.json`.
|
|
26
|
+
* @returns {Record<string, string>} Notes by exact key name.
|
|
27
|
+
*/
|
|
28
|
+
export function loadNotes(notesFile) {
|
|
29
|
+
if (!existsSync(notesFile)) {
|
|
30
|
+
throw new Error(
|
|
31
|
+
`secret notes are unavailable at ${notesFile}.\n` +
|
|
32
|
+
`Run the remote environment bootstrap before using a credential.`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
const payload = JSON.parse(readFileSync(notesFile, "utf8"));
|
|
36
|
+
if (payload.schemaVersion !== 1 || typeof payload.secrets !== "object") {
|
|
37
|
+
throw new Error("unsupported or malformed secret-notes manifest");
|
|
38
|
+
}
|
|
39
|
+
return payload.secrets;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Resolve one note, treating absence as a stop condition rather than a default.
|
|
44
|
+
*
|
|
45
|
+
* A missing note is deliberately fatal for the credential that lacks it. The
|
|
46
|
+
* alternative — carrying on with an inferred purpose — is how a token scoped to
|
|
47
|
+
* one repository gets used against another, and the failure is silent.
|
|
48
|
+
* @param {string} name Exact environment-variable name.
|
|
49
|
+
* @param {Record<string, string>} notes Loaded manifest entries.
|
|
50
|
+
* @returns {string} The note text.
|
|
51
|
+
*/
|
|
52
|
+
export function noteFor(name, notes) {
|
|
53
|
+
if (!ENV_KEY.test(name)) {
|
|
54
|
+
throw new Error("secret key must be an exact environment-variable name");
|
|
55
|
+
}
|
|
56
|
+
const note = notes[name];
|
|
57
|
+
if (typeof note !== "string" || !note.trim()) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`no usable note exists for ${name}.\n` +
|
|
60
|
+
`A missing or ambiguous note is a stop condition for that credential — ` +
|
|
61
|
+
`do not infer its purpose from its name.`
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
return note;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function main() {
|
|
68
|
+
const [name] = process.argv.slice(2);
|
|
69
|
+
if (!name) throw new Error("usage: read-secret-note.mjs NAME");
|
|
70
|
+
const cfg = readConfig();
|
|
71
|
+
const { notesFile } = materializedPaths(cfg.namespace);
|
|
72
|
+
console.log(noteFor(name, loadNotes(notesFile)));
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (import.meta.url === `file://${process.argv[1]}`) {
|
|
76
|
+
try {
|
|
77
|
+
main();
|
|
78
|
+
} catch (err) {
|
|
79
|
+
console.error(err.message);
|
|
80
|
+
process.exit(1);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
@@ -1,15 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
* Resolve a secret through the configured provider.
|
|
3
|
+
* Resolve a secret through the configured provider and surface.
|
|
4
4
|
*
|
|
5
5
|
* The executable half of `lisa-secrets-access`. It exists as a CLI rather than
|
|
6
6
|
* a library because the callers are polyglot — Python scripts, shell, and
|
|
7
7
|
* TypeScript all need the same answer, and a subprocess is the only interface
|
|
8
8
|
* all three share without duplicating the logic three times.
|
|
9
9
|
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
10
|
+
* The resolution ladder is one rule with one order:
|
|
11
|
+
*
|
|
12
|
+
* environment -> materialized file (surfaces that have one) -> provider
|
|
13
|
+
*
|
|
14
|
+
* Environment comes first so a CI run, where the pipeline injects secrets,
|
|
15
|
+
* never reaches for a provider or a local store at all. The middle rung exists
|
|
16
|
+
* only on surfaces whose bootstrap runs before the consuming process does.
|
|
13
17
|
*
|
|
14
18
|
* Values are written to stdout with no trailing newline and nothing else, so
|
|
15
19
|
* `$(resolve-secret.mjs get NAME)` is safe. Diagnostics go to stderr.
|
|
@@ -19,160 +23,65 @@
|
|
|
19
23
|
* resolve-secret.mjs list
|
|
20
24
|
* resolve-secret.mjs describe NAME
|
|
21
25
|
* resolve-secret.mjs verify
|
|
26
|
+
* resolve-secret.mjs surface
|
|
27
|
+
* @module resolve-secret
|
|
22
28
|
*/
|
|
23
29
|
|
|
24
|
-
import { execFileSync } from "node:child_process";
|
|
25
30
|
import { existsSync, readFileSync } from "node:fs";
|
|
26
|
-
import { join } from "node:path";
|
|
27
31
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
bootstrap: { sources: ["env"], key: null },
|
|
32
|
-
require: null,
|
|
33
|
-
};
|
|
32
|
+
import { parseEnv } from "./envfile.mjs";
|
|
33
|
+
import { ENV_KEY, fetchAll } from "./providers.mjs";
|
|
34
|
+
import { materializedPaths, readConfig } from "./surfaces.mjs";
|
|
34
35
|
|
|
35
|
-
|
|
36
|
-
* Read the `secrets` block from `.lisa.config.json`.
|
|
37
|
-
* @param {string} cwd Directory to look in.
|
|
38
|
-
* @returns {{provider: string, bootstrap: {sources: string[], key: string|null}, require: string[]|null}}
|
|
39
|
-
*/
|
|
40
|
-
export function readConfig(cwd = process.cwd()) {
|
|
41
|
-
const path = join(cwd, ".lisa.config.json");
|
|
42
|
-
if (!existsSync(path)) return DEFAULTS;
|
|
43
|
-
try {
|
|
44
|
-
const cfg = JSON.parse(readFileSync(path, "utf8")).secrets;
|
|
45
|
-
if (!cfg) return DEFAULTS;
|
|
46
|
-
return {
|
|
47
|
-
provider: cfg.provider ?? DEFAULTS.provider,
|
|
48
|
-
bootstrap: { ...DEFAULTS.bootstrap, ...(cfg.bootstrap ?? {}) },
|
|
49
|
-
require: cfg.require ?? null,
|
|
50
|
-
};
|
|
51
|
-
} catch (err) {
|
|
52
|
-
throw new Error(`.lisa.config.json is not readable: ${err.message}`);
|
|
53
|
-
}
|
|
54
|
-
}
|
|
36
|
+
export { readConfig };
|
|
55
37
|
|
|
56
38
|
/**
|
|
57
|
-
*
|
|
39
|
+
* Read the materialized values file, if this surface has one.
|
|
58
40
|
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
* @param {
|
|
62
|
-
* @returns {string
|
|
41
|
+
* A surface without the capability returns empty rather than throwing: the
|
|
42
|
+
* absence of a middle rung is normal, not an error.
|
|
43
|
+
* @param {object} cfg Resolved configuration.
|
|
44
|
+
* @returns {Map<string, string>} Values by exact name.
|
|
63
45
|
*/
|
|
64
|
-
function
|
|
65
|
-
if (!
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (v) return v;
|
|
70
|
-
}
|
|
71
|
-
if (source === "keychain" && process.platform === "darwin") {
|
|
72
|
-
try {
|
|
73
|
-
const v = execFileSync(
|
|
74
|
-
"security",
|
|
75
|
-
[
|
|
76
|
-
"find-generic-password",
|
|
77
|
-
"-s",
|
|
78
|
-
bootstrap.key,
|
|
79
|
-
"-a",
|
|
80
|
-
process.env.USER ?? "",
|
|
81
|
-
"-w",
|
|
82
|
-
],
|
|
83
|
-
{ encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
|
|
84
|
-
).trim();
|
|
85
|
-
if (v) return v;
|
|
86
|
-
} catch {
|
|
87
|
-
/* not present in the keychain; try the next source */
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
throw new Error(
|
|
92
|
-
`${bootstrap.key} not found in: ${bootstrap.sources.join(", ")}.\n` +
|
|
93
|
-
`It is the bootstrap credential — without it no other secret can be read.`
|
|
94
|
-
);
|
|
46
|
+
export function readMaterialized(cfg) {
|
|
47
|
+
if (!cfg.capabilities.materialized) return new Map();
|
|
48
|
+
const { valuesFile } = materializedPaths(cfg.namespace);
|
|
49
|
+
if (!existsSync(valuesFile)) return new Map();
|
|
50
|
+
return parseEnv(readFileSync(valuesFile, "utf8"));
|
|
95
51
|
}
|
|
96
52
|
|
|
97
53
|
/**
|
|
98
|
-
*
|
|
54
|
+
* Assert a name is declared when the project narrows itself with `require`.
|
|
99
55
|
*
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
*
|
|
104
|
-
* @param {
|
|
105
|
-
* @returns {Map<string, {value: string, note: string}>} Secrets by name.
|
|
56
|
+
* A `require` list is an assertion, not merely a filter: naming a secret
|
|
57
|
+
* declares the project needs it, so asking for one outside the list is a
|
|
58
|
+
* configuration error rather than a lookup miss.
|
|
59
|
+
* @param {string} name Requested name.
|
|
60
|
+
* @param {object} cfg Resolved configuration.
|
|
106
61
|
*/
|
|
107
|
-
function
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
switch (cfg.provider) {
|
|
114
|
-
case "env":
|
|
115
|
-
for (const [k, v] of Object.entries(process.env)) {
|
|
116
|
-
if (/^[A-Z][A-Z0-9_]*$/.test(k))
|
|
117
|
-
out.set(k, { value: v ?? "", note: "" });
|
|
118
|
-
}
|
|
119
|
-
return out;
|
|
120
|
-
|
|
121
|
-
case "bitwarden": {
|
|
122
|
-
const raw = execFileSync("bws", ["secret", "list", "--output", "json"], {
|
|
123
|
-
encoding: "utf8",
|
|
124
|
-
env,
|
|
125
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
126
|
-
});
|
|
127
|
-
for (const s of JSON.parse(raw || "[]")) {
|
|
128
|
-
out.set(s.key, { value: s.value, note: s.note ?? "" });
|
|
129
|
-
}
|
|
130
|
-
return out;
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
case "doppler": {
|
|
134
|
-
const raw = execFileSync(
|
|
135
|
-
"doppler",
|
|
136
|
-
["secrets", "download", "--no-file", "--format", "json"],
|
|
137
|
-
{
|
|
138
|
-
encoding: "utf8",
|
|
139
|
-
env,
|
|
140
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
141
|
-
}
|
|
142
|
-
);
|
|
143
|
-
for (const [k, v] of Object.entries(JSON.parse(raw || "{}"))) {
|
|
144
|
-
out.set(k, { value: String(v), note: "" });
|
|
145
|
-
}
|
|
146
|
-
return out;
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
default:
|
|
150
|
-
throw new Error(
|
|
151
|
-
`provider "${cfg.provider}" has no bulk read implemented yet.\n` +
|
|
152
|
-
`Add one in resolve-secret.mjs — see the dispatch table in SKILL.md.`
|
|
153
|
-
);
|
|
62
|
+
function assertDeclared(name, cfg) {
|
|
63
|
+
if (cfg.require && !cfg.require.includes(name)) {
|
|
64
|
+
throw new Error(
|
|
65
|
+
`${name} is not declared in secrets.require.\n` +
|
|
66
|
+
`Declared: ${cfg.require.join(", ") || "(none)"}`
|
|
67
|
+
);
|
|
154
68
|
}
|
|
155
69
|
}
|
|
156
70
|
|
|
157
71
|
/**
|
|
158
|
-
* Resolve one secret
|
|
72
|
+
* Resolve one secret down the ladder.
|
|
159
73
|
* @param {string} name Environment-variable-style key.
|
|
160
|
-
* @param {
|
|
74
|
+
* @param {object} [cfg] Resolved configuration.
|
|
161
75
|
* @returns {string} The secret value.
|
|
162
76
|
*/
|
|
163
77
|
export function get(name, cfg = readConfig()) {
|
|
164
78
|
const fromEnv = (process.env[name] ?? "").trim();
|
|
165
79
|
if (fromEnv) return fromEnv;
|
|
166
80
|
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
if (
|
|
171
|
-
throw new Error(
|
|
172
|
-
`${name} is not declared in secrets.require.\n` +
|
|
173
|
-
`Declared: ${cfg.require.join(", ") || "(none)"}`
|
|
174
|
-
);
|
|
175
|
-
}
|
|
81
|
+
assertDeclared(name, cfg);
|
|
82
|
+
|
|
83
|
+
const fromFile = readMaterialized(cfg).get(name);
|
|
84
|
+
if (fromFile) return fromFile;
|
|
176
85
|
|
|
177
86
|
const all = fetchAll(cfg);
|
|
178
87
|
const hit = all.get(name);
|
|
@@ -186,10 +95,108 @@ export function get(name, cfg = readConfig()) {
|
|
|
186
95
|
return hit.value;
|
|
187
96
|
}
|
|
188
97
|
|
|
98
|
+
/**
|
|
99
|
+
* Report which rung of the ladder answers for a name, without revealing values.
|
|
100
|
+
* @param {string} name Requested name.
|
|
101
|
+
* @param {Map<string, object>} provider Provider view.
|
|
102
|
+
* @param {Map<string, string>} file Materialized view.
|
|
103
|
+
* @returns {string} A fixed-width source label.
|
|
104
|
+
*/
|
|
105
|
+
function sourceOf(name, provider, file) {
|
|
106
|
+
if ((process.env[name] ?? "").trim()) return "env ";
|
|
107
|
+
if (file.get(name)) return "file ";
|
|
108
|
+
if (provider.get(name)?.value) return "provider ";
|
|
109
|
+
return " ";
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Verify every declared secret, mirroring the ladder's own order.
|
|
114
|
+
*
|
|
115
|
+
* Checking only the provider would report MISSING for every secret in CI, where
|
|
116
|
+
* the pipeline injects them as environment variables — a false negative exactly
|
|
117
|
+
* where verification matters most.
|
|
118
|
+
*
|
|
119
|
+
* This command is read-only. Proving that a rotating credential can actually be
|
|
120
|
+
* written back requires a write, so that check lives in the rotation path
|
|
121
|
+
* rather than here.
|
|
122
|
+
* @param {object} cfg Resolved configuration.
|
|
123
|
+
* @returns {number} Count of secrets that failed.
|
|
124
|
+
*/
|
|
125
|
+
function verify(cfg) {
|
|
126
|
+
const provider = fetchAll(cfg);
|
|
127
|
+
const file = readMaterialized(cfg);
|
|
128
|
+
const names = cfg.require ?? [
|
|
129
|
+
...new Set([...provider.keys(), ...file.keys()]),
|
|
130
|
+
];
|
|
131
|
+
const rotating = new Set(cfg.rotating ?? []);
|
|
132
|
+
let bad = 0;
|
|
133
|
+
|
|
134
|
+
console.log(`surface: ${cfg.surface} provider: ${cfg.provider}`);
|
|
135
|
+
for (const name of names) {
|
|
136
|
+
const resolves = Boolean(
|
|
137
|
+
(process.env[name] ?? "").trim() ||
|
|
138
|
+
file.get(name) ||
|
|
139
|
+
provider.get(name)?.value
|
|
140
|
+
);
|
|
141
|
+
const named = ENV_KEY.test(name);
|
|
142
|
+
const noted = Boolean(provider.get(name)?.note);
|
|
143
|
+
if (!resolves || !named || !noted) bad += 1;
|
|
144
|
+
console.log(
|
|
145
|
+
` ${name.padEnd(30)} ${resolves ? "resolves" : "MISSING "} ` +
|
|
146
|
+
`${sourceOf(name, provider, file)} ` +
|
|
147
|
+
`${named ? "name ok" : "NAME NOT UPPER_SNAKE"} ` +
|
|
148
|
+
`${noted ? "noted" : "NO NOTE"}` +
|
|
149
|
+
`${rotating.has(name) ? " rotating" : ""}`
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
for (const name of rotating) {
|
|
154
|
+
if (!cfg.bootstrap.key) {
|
|
155
|
+
console.log(
|
|
156
|
+
` ${name.padEnd(30)} ROTATING BUT NO BOOTSTRAP — the replacement could ` +
|
|
157
|
+
`not be written back, which strands the credential on first use`
|
|
158
|
+
);
|
|
159
|
+
bad += 1;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
return bad;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Describe a secret's usage note without ever printing its value.
|
|
167
|
+
* @param {string} name Requested name.
|
|
168
|
+
* @param {object} cfg Resolved configuration.
|
|
169
|
+
*/
|
|
170
|
+
function describe(name, cfg) {
|
|
171
|
+
const hit = fetchAll(cfg).get(name);
|
|
172
|
+
if (hit) {
|
|
173
|
+
// Infer *and* warn, never instead of. A silent fallback that works well
|
|
174
|
+
// enough is what guarantees the notes stay empty forever.
|
|
175
|
+
console.log(
|
|
176
|
+
hit.note ||
|
|
177
|
+
`(no note — purpose inferred from the name: ${name}. ` +
|
|
178
|
+
`An inferred mapping must never authorise a write.)`
|
|
179
|
+
);
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
// Environment-only is a legitimate state, not an error: CI injects secrets
|
|
183
|
+
// that never appear in a provider listing. Say so rather than claiming the
|
|
184
|
+
// secret does not exist, and never print the value.
|
|
185
|
+
if ((process.env[name] ?? "").trim()) {
|
|
186
|
+
console.log("(set in the environment; no provider entry, so no note)");
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
throw new Error(`${name} not found in the environment or the provider`);
|
|
190
|
+
}
|
|
191
|
+
|
|
189
192
|
function main() {
|
|
190
193
|
const [op, name] = process.argv.slice(2);
|
|
191
194
|
const cfg = readConfig();
|
|
192
195
|
|
|
196
|
+
if (op === "surface") {
|
|
197
|
+
console.log(cfg.surface);
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
193
200
|
if (op === "get") {
|
|
194
201
|
if (!name) throw new Error("usage: resolve-secret.mjs get NAME");
|
|
195
202
|
process.stdout.write(get(name, cfg));
|
|
@@ -197,58 +204,26 @@ function main() {
|
|
|
197
204
|
}
|
|
198
205
|
if (op === "list") {
|
|
199
206
|
// Names only. This command must never be able to leak a value.
|
|
200
|
-
|
|
207
|
+
const names = new Set([
|
|
208
|
+
...fetchAll(cfg).keys(),
|
|
209
|
+
...readMaterialized(cfg).keys(),
|
|
210
|
+
]);
|
|
211
|
+
console.log([...names].sort().join("\n"));
|
|
201
212
|
return;
|
|
202
213
|
}
|
|
203
214
|
if (op === "describe") {
|
|
204
215
|
if (!name) throw new Error("usage: resolve-secret.mjs describe NAME");
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
console.log(
|
|
208
|
-
hit.note || `(no note — purpose inferred from the name: ${name})`
|
|
209
|
-
);
|
|
210
|
-
return;
|
|
211
|
-
}
|
|
212
|
-
// Environment-only is a legitimate state, not an error: CI injects secrets
|
|
213
|
-
// that never appear in the provider listing. Say so rather than claiming
|
|
214
|
-
// the secret does not exist, and never print the value.
|
|
215
|
-
if ((process.env[name] ?? "").trim()) {
|
|
216
|
-
console.log(
|
|
217
|
-
`(set in the environment; no provider entry, so no note is available)`
|
|
218
|
-
);
|
|
219
|
-
return;
|
|
220
|
-
}
|
|
221
|
-
throw new Error(`${name} not found in the environment or the provider`);
|
|
216
|
+
describe(name, cfg);
|
|
217
|
+
return;
|
|
222
218
|
}
|
|
223
219
|
if (op === "verify") {
|
|
224
|
-
const
|
|
225
|
-
const names = cfg.require ?? [...all.keys()];
|
|
226
|
-
let bad = 0;
|
|
227
|
-
for (const n of names) {
|
|
228
|
-
// Mirror get()'s environment-first order. Checking only the provider
|
|
229
|
-
// reports MISSING for every secret in CI, where the pipeline injects
|
|
230
|
-
// them as environment variables — a false negative precisely where
|
|
231
|
-
// verification matters most.
|
|
232
|
-
const fromEnv = Boolean((process.env[n] ?? "").trim());
|
|
233
|
-
const entry = all.get(n);
|
|
234
|
-
const ok = fromEnv || Boolean(entry?.value);
|
|
235
|
-
const named = /^[A-Z][A-Z0-9_]*$/.test(n);
|
|
236
|
-
const noted = Boolean(entry?.note);
|
|
237
|
-
if (!ok || !named) bad += 1;
|
|
238
|
-
const source = fromEnv
|
|
239
|
-
? "env "
|
|
240
|
-
: entry?.value
|
|
241
|
-
? "provider"
|
|
242
|
-
: " ";
|
|
243
|
-
console.log(
|
|
244
|
-
` ${n.padEnd(30)} ${ok ? "resolves" : "MISSING "} ${source} ` +
|
|
245
|
-
`${named ? "name ok" : "NAME NOT UPPER_SNAKE"} ${noted ? "noted" : "no note"}`
|
|
246
|
-
);
|
|
247
|
-
}
|
|
220
|
+
const bad = verify(cfg);
|
|
248
221
|
if (bad) throw new Error(`${bad} secret(s) failed verification`);
|
|
249
222
|
return;
|
|
250
223
|
}
|
|
251
|
-
throw new Error(
|
|
224
|
+
throw new Error(
|
|
225
|
+
"usage: resolve-secret.mjs get|list|describe|verify|surface [NAME]"
|
|
226
|
+
);
|
|
252
227
|
}
|
|
253
228
|
|
|
254
229
|
if (import.meta.url === `file://${process.argv[1]}`) {
|