@codyswann/lisa 2.316.2 → 2.317.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 (212) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +139 -9
  3. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +17 -0
  8. package/plugins/lisa/.codex-plugin/skills/lisa-automation-status/SKILL.md +23 -0
  9. package/plugins/lisa/.codex-plugin/skills/lisa-doctor/SKILL.md +33 -0
  10. package/plugins/lisa/.codex-plugin/skills/lisa-implement/SKILL.md +17 -0
  11. package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +17 -0
  12. package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/SKILL.md +106 -0
  13. package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/agents/openai.yaml +4 -0
  14. package/plugins/lisa/.codex-plugin/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  15. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +137 -51
  16. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  17. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  18. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  19. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  20. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  21. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  22. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  23. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  24. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  25. package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/SKILL.md +69 -0
  26. package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  27. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/SKILL.md +150 -0
  28. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/agents/openai.yaml +4 -0
  29. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  30. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  31. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  32. package/plugins/lisa/.codex-plugin/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  33. package/plugins/lisa/commands/implement.md +5 -3
  34. package/plugins/lisa/commands/setup/remote-env.md +7 -0
  35. package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +17 -0
  36. package/plugins/lisa/skills/lisa-automation-status/SKILL.md +23 -0
  37. package/plugins/lisa/skills/lisa-doctor/SKILL.md +33 -0
  38. package/plugins/lisa/skills/lisa-implement/SKILL.md +17 -0
  39. package/plugins/lisa/skills/lisa-notion-access/SKILL.md +17 -0
  40. package/plugins/lisa/skills/lisa-remote-dispatch/SKILL.md +106 -0
  41. package/plugins/lisa/skills/lisa-remote-dispatch/agents/openai.yaml +4 -0
  42. package/plugins/lisa/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  43. package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +138 -52
  44. package/plugins/lisa/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  45. package/plugins/lisa/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  46. package/plugins/lisa/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  47. package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  48. package/plugins/lisa/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  49. package/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  50. package/plugins/lisa/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  51. package/plugins/lisa/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  52. package/plugins/lisa/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  53. package/plugins/lisa/skills/lisa-setup-automations/SKILL.md +69 -0
  54. package/plugins/lisa/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  55. package/plugins/lisa/skills/lisa-setup-remote-env/SKILL.md +150 -0
  56. package/plugins/lisa/skills/lisa-setup-remote-env/agents/openai.yaml +4 -0
  57. package/plugins/lisa/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  58. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  59. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  60. package/plugins/lisa/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  61. package/plugins/lisa-agy/commands/lisa/implement.md +5 -3
  62. package/plugins/lisa-agy/commands/lisa/setup/remote-env.md +7 -0
  63. package/plugins/lisa-agy/plugin.json +1 -1
  64. package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +17 -0
  65. package/plugins/lisa-agy/skills/lisa-automation-status/SKILL.md +23 -0
  66. package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +33 -0
  67. package/plugins/lisa-agy/skills/lisa-implement/SKILL.md +17 -0
  68. package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +17 -0
  69. package/plugins/lisa-agy/skills/lisa-remote-dispatch/SKILL.md +106 -0
  70. package/plugins/lisa-agy/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  71. package/plugins/lisa-agy/skills/lisa-secrets-access/SKILL.md +138 -52
  72. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  73. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  74. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  75. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  76. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  77. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  78. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  79. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  80. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  81. package/plugins/lisa-agy/skills/lisa-setup-automations/SKILL.md +69 -0
  82. package/plugins/lisa-agy/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  83. package/plugins/lisa-agy/skills/lisa-setup-remote-env/SKILL.md +150 -0
  84. package/plugins/lisa-agy/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  85. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  86. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  87. package/plugins/lisa-agy/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  88. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  91. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-copilot/commands/lisa/implement.md +5 -3
  95. package/plugins/lisa-copilot/commands/lisa/setup/remote-env.md +7 -0
  96. package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +17 -0
  97. package/plugins/lisa-copilot/skills/lisa-automation-status/SKILL.md +23 -0
  98. package/plugins/lisa-copilot/skills/lisa-doctor/SKILL.md +33 -0
  99. package/plugins/lisa-copilot/skills/lisa-implement/SKILL.md +17 -0
  100. package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +17 -0
  101. package/plugins/lisa-copilot/skills/lisa-remote-dispatch/SKILL.md +106 -0
  102. package/plugins/lisa-copilot/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  103. package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +138 -52
  104. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  105. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  106. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  107. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  108. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  109. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  110. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  111. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  112. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  113. package/plugins/lisa-copilot/skills/lisa-setup-automations/SKILL.md +69 -0
  114. package/plugins/lisa-copilot/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  115. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/SKILL.md +150 -0
  116. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  117. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  118. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  119. package/plugins/lisa-copilot/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  120. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  121. package/plugins/lisa-cursor/commands/lisa/implement.md +5 -3
  122. package/plugins/lisa-cursor/commands/lisa/setup/remote-env.md +7 -0
  123. package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +17 -0
  124. package/plugins/lisa-cursor/skills/lisa-automation-status/SKILL.md +23 -0
  125. package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +33 -0
  126. package/plugins/lisa-cursor/skills/lisa-implement/SKILL.md +17 -0
  127. package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +17 -0
  128. package/plugins/lisa-cursor/skills/lisa-remote-dispatch/SKILL.md +106 -0
  129. package/plugins/lisa-cursor/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  130. package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +138 -52
  131. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  132. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  133. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  134. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  135. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  136. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  137. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  138. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  139. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  140. package/plugins/lisa-cursor/skills/lisa-setup-automations/SKILL.md +69 -0
  141. package/plugins/lisa-cursor/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  142. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/SKILL.md +150 -0
  143. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  144. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  145. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  146. package/plugins/lisa-cursor/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
  147. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  148. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  149. package/plugins/lisa-expo-agy/plugin.json +1 -1
  150. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  151. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  152. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  153. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  154. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  155. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  156. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  157. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  158. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  159. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  160. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  161. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  162. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  163. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  164. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  165. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  166. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  167. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  168. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  169. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  170. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  171. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  172. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  173. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  174. package/plugins/lisa-rails-agy/plugin.json +1 -1
  175. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  176. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  177. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  178. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  179. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  180. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  181. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  182. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  183. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  184. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  185. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  186. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  187. package/plugins/src/base/commands/implement.md +5 -3
  188. package/plugins/src/base/commands/setup/remote-env.md +7 -0
  189. package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +17 -0
  190. package/plugins/src/base/skills/lisa-automation-status/SKILL.md +23 -0
  191. package/plugins/src/base/skills/lisa-doctor/SKILL.md +33 -0
  192. package/plugins/src/base/skills/lisa-implement/SKILL.md +17 -0
  193. package/plugins/src/base/skills/lisa-notion-access/SKILL.md +17 -0
  194. package/plugins/src/base/skills/lisa-remote-dispatch/SKILL.md +106 -0
  195. package/plugins/src/base/skills/lisa-remote-dispatch/scripts/dispatch.mjs +264 -0
  196. package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +138 -52
  197. package/plugins/src/base/skills/lisa-secrets-access/scripts/doctor-secrets.mjs +217 -0
  198. package/plugins/src/base/skills/lisa-secrets-access/scripts/envfile.mjs +78 -0
  199. package/plugins/src/base/skills/lisa-secrets-access/scripts/materialize-secrets.mjs +105 -0
  200. package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +240 -0
  201. package/plugins/src/base/skills/lisa-secrets-access/scripts/read-secret-note.mjs +82 -0
  202. package/plugins/src/base/skills/lisa-secrets-access/scripts/resolve-secret.mjs +151 -176
  203. package/plugins/src/base/skills/lisa-secrets-access/scripts/rotate-secret.mjs +266 -0
  204. package/plugins/src/base/skills/lisa-secrets-access/scripts/surfaces.mjs +147 -0
  205. package/plugins/src/base/skills/lisa-secrets-access/scripts/validate-config.mjs +224 -0
  206. package/plugins/src/base/skills/lisa-setup-automations/SKILL.md +69 -0
  207. package/plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs +185 -0
  208. package/plugins/src/base/skills/lisa-setup-remote-env/SKILL.md +150 -0
  209. package/plugins/src/base/skills/lisa-setup-remote-env/assets/setup.sh +45 -0
  210. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/setup-remote-env.mjs +227 -0
  211. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/toolchain.mjs +169 -0
  212. package/plugins/src/base/skills/lisa-setup-remote-env/scripts/verify-remote-env.mjs +186 -0
@@ -0,0 +1,240 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Provider reads and boundary enforcement for `lisa-secrets-access`.
4
+ *
5
+ * Retrieval and selection are separated deliberately. `fetchRaw` is the only
6
+ * function that touches a provider; everything downstream operates on plain
7
+ * rows, which is what makes the boundary rules testable with synthetic data and
8
+ * without granting a test process access to any real secret.
9
+ * @module providers
10
+ */
11
+
12
+ import { execFileSync } from "node:child_process";
13
+
14
+ /**
15
+ * A provider key becomes a shell variable name on materializing surfaces, so
16
+ * only names valid in every POSIX-like shell are accepted. Anything else stays
17
+ * in the provider and is intentionally not exported.
18
+ */
19
+ export const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
20
+
21
+ /**
22
+ * The provider entry holding rotation leases.
23
+ *
24
+ * Leases live in the provider because it is the only substrate every surface
25
+ * shares — a CI concurrency group and a laptop lockfile cannot see each other,
26
+ * but both can see this. It is coordination state rather than a credential, so
27
+ * it is excluded from every selection: nothing should resolve or materialize it.
28
+ */
29
+ export const LEASE_KEY = "LISA_ROTATION_LEASES";
30
+
31
+ /**
32
+ * Obtain the one credential that unlocks the provider.
33
+ *
34
+ * Walks `sources` in order, environment first, so a CI run where the pipeline
35
+ * injects the bootstrap never reaches for a local store. This is the only
36
+ * credential permitted in an OS keychain — it is a bootstrap, not a cached copy
37
+ * of anything downstream.
38
+ * @param {{sources: string[], key: string|null}} bootstrap Bootstrap config.
39
+ * @returns {string|null} The token, or null when the provider needs none.
40
+ */
41
+ export function bootstrapToken(bootstrap) {
42
+ if (!bootstrap.key) return null;
43
+ for (const source of bootstrap.sources) {
44
+ const found =
45
+ source === "env"
46
+ ? (process.env[bootstrap.key] ?? "").trim()
47
+ : source === "keychain"
48
+ ? fromKeychain(bootstrap.key)
49
+ : "";
50
+ if (found) return found;
51
+ }
52
+ throw new Error(
53
+ `${bootstrap.key} not found in: ${bootstrap.sources.join(", ")}.\n` +
54
+ `It is the bootstrap credential — without it no other secret can be read.`
55
+ );
56
+ }
57
+
58
+ /**
59
+ * Read one value from the macOS keychain, treating absence as empty.
60
+ * @param {string} key Keychain service name.
61
+ * @returns {string} The value, or an empty string when unavailable.
62
+ */
63
+ function fromKeychain(key) {
64
+ if (process.platform !== "darwin") return "";
65
+ try {
66
+ return execFileSync(
67
+ "security",
68
+ ["find-generic-password", "-s", key, "-a", process.env.USER ?? "", "-w"],
69
+ { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }
70
+ ).trim();
71
+ } catch {
72
+ return "";
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Read every secret the provider grants, as unfiltered rows.
78
+ *
79
+ * Deliberately reads all rather than one: the provider already scopes what this
80
+ * caller may see, so the set it returns *is* the permitted set. Restating that
81
+ * as a list in config would duplicate a boundary the provider already enforces.
82
+ * @param {object} cfg Resolved configuration.
83
+ * @returns {Array<{key: string, value: string, note: string, projectId: string|null}>} Rows.
84
+ */
85
+ export function fetchRaw(cfg) {
86
+ const env = { ...process.env };
87
+ const token = bootstrapToken(cfg.bootstrap);
88
+ if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
89
+
90
+ if (cfg.provider === "env") {
91
+ return Object.entries(process.env)
92
+ .filter(([k]) => /^[A-Z][A-Z0-9_]*$/.test(k))
93
+ .map(([key, value]) => ({
94
+ key,
95
+ value: value ?? "",
96
+ note: "",
97
+ projectId: null,
98
+ }));
99
+ }
100
+
101
+ if (cfg.provider === "bitwarden") {
102
+ const raw = run("bws", ["secret", "list", "--output", "json"], env);
103
+ return JSON.parse(raw || "[]").map(s => ({
104
+ key: s.key,
105
+ value: s.value,
106
+ note: s.note ?? "",
107
+ projectId: s.projectId ?? null,
108
+ id: s.id ?? null,
109
+ }));
110
+ }
111
+
112
+ if (cfg.provider === "doppler") {
113
+ const args = ["secrets", "download", "--no-file", "--format", "json"];
114
+ const raw = run("doppler", args, env);
115
+ return Object.entries(JSON.parse(raw || "{}")).map(([key, value]) => ({
116
+ key,
117
+ value: String(value),
118
+ note: "",
119
+ projectId: null,
120
+ }));
121
+ }
122
+
123
+ throw new Error(
124
+ `provider "${cfg.provider}" has no bulk read implemented yet.\n` +
125
+ `Add one in providers.mjs — see the dispatch table in SKILL.md.`
126
+ );
127
+ }
128
+
129
+ /**
130
+ * Run a provider CLI, keeping its output off any shared stream.
131
+ * @param {string} bin Executable name.
132
+ * @param {string[]} args Arguments.
133
+ * @param {NodeJS.ProcessEnv} env Environment for the child.
134
+ * @returns {string} Captured stdout.
135
+ */
136
+ function run(bin, args, env) {
137
+ return execFileSync(bin, args, {
138
+ encoding: "utf8",
139
+ env,
140
+ stdio: ["ignore", "pipe", "pipe"],
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Apply the exposure boundary and reject an ambiguous set.
146
+ *
147
+ * The provider's own scoping is the default allowlist. The optional controls
148
+ * may only *narrow* it — there is intentionally no way to widen access from
149
+ * config, because that boundary belongs to the provider grant.
150
+ *
151
+ * A duplicate exact-name key is fatal rather than last-wins. Silently choosing
152
+ * one would make which credential gets used depend on provider response order,
153
+ * which is neither stable nor visible at the call site.
154
+ * @param {Array<object>} rows Raw provider rows.
155
+ * @param {{projectIds: string[], excludeKeys: string[]}} narrow Narrowing controls.
156
+ * @returns {Map<string, {value: string, note: string, id: string|null}>} Selected secrets by name.
157
+ */
158
+ export function normalizeRows(
159
+ rows,
160
+ narrow = { projectIds: [], excludeKeys: [] }
161
+ ) {
162
+ const projects = new Set(narrow.projectIds ?? []);
163
+ const excluded = new Set([...(narrow.excludeKeys ?? []), LEASE_KEY]);
164
+ const out = new Map();
165
+
166
+ for (const row of rows) {
167
+ if (projects.size && !projects.has(row.projectId)) continue;
168
+ if (typeof row.key !== "string" || !ENV_KEY.test(row.key)) continue;
169
+ if (excluded.has(row.key)) continue;
170
+ if (out.has(row.key)) {
171
+ throw new Error(
172
+ `duplicate secret key across visible projects: ${row.key}.\n` +
173
+ `Resolve it at the provider — choosing one here would make credential ` +
174
+ `use depend on response order.`
175
+ );
176
+ }
177
+ if (typeof row.value !== "string") {
178
+ throw new Error(`secret value is not a string: ${row.key}`);
179
+ }
180
+ out.set(row.key, {
181
+ value: row.value,
182
+ note: row.note ?? "",
183
+ id: row.id ?? null,
184
+ });
185
+ }
186
+ return out;
187
+ }
188
+
189
+ /**
190
+ * Read and select in one step — the normal entry point for consumers.
191
+ * @param {object} cfg Resolved configuration.
192
+ * @returns {Map<string, {value: string, note: string, id: string|null}>} Selected secrets.
193
+ */
194
+ export function fetchAll(cfg) {
195
+ return normalizeRows(fetchRaw(cfg), cfg.narrow);
196
+ }
197
+
198
+ /**
199
+ * Find one raw row by exact key, bypassing the exposure boundary.
200
+ *
201
+ * Only the rotation path uses this, and only to reach the lease record, which
202
+ * is deliberately excluded from every normal selection. Reading a *credential*
203
+ * must always go through {@link fetchAll} so the boundary applies.
204
+ * @param {object} cfg Resolved configuration.
205
+ * @param {string} key Exact key name.
206
+ * @returns {object|null} The raw row, or null when absent.
207
+ */
208
+ export function rawByKey(cfg, key) {
209
+ const matches = fetchRaw(cfg).filter(row => row.key === key);
210
+ if (matches.length > 1) throw new Error(`duplicate secret key: ${key}`);
211
+ return matches[0] ?? null;
212
+ }
213
+
214
+ /**
215
+ * Write a replacement value back to the provider.
216
+ *
217
+ * Passing a value as an argument is normally forbidden, because process
218
+ * arguments are visible to anyone who can run `ps` on the same host. The
219
+ * Bitwarden CLI exposes no stdin path for an edit, so this is a documented
220
+ * exception rather than an oversight: it is confined to the one operation that
221
+ * cannot be expressed otherwise, and `execFileSync` at least keeps the value
222
+ * out of a shell and therefore out of shell history.
223
+ * @param {object} cfg Resolved configuration.
224
+ * @param {string} id Provider-side identifier for the secret.
225
+ * @param {string} value Replacement value.
226
+ */
227
+ export function writeSecret(cfg, id, value) {
228
+ const env = { ...process.env };
229
+ const token = bootstrapToken(cfg.bootstrap);
230
+ if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
231
+
232
+ if (cfg.provider === "bitwarden") {
233
+ run("bws", ["secret", "edit", id, "--value", value], env);
234
+ return;
235
+ }
236
+ throw new Error(
237
+ `provider "${cfg.provider}" has no write implemented.\n` +
238
+ `Rotation requires a proven write path; add one in providers.mjs.`
239
+ );
240
+ }
@@ -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
+ }