@agentproto/secrets 0.1.0-alpha.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 agentproto contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # @agentproto/secrets
2
+
3
+ AIP-19 `SECRETS.md` reference implementation. A workspace-level manifest format for declaring secret slugs, their purpose, access grants, and audit metadata — without ever storing the values themselves. Hosts resolve slugs against a real vault at reveal time.
4
+
5
+ > **Status: 0.1.0-alpha.** Generated by `scripts/scaffold-aip.mjs` — `build()` and `validate()` bodies are TODOs.
6
+
7
+ Spec: <https://agentproto.sh/docs/aip-19>
8
+
9
+ ## Usage
10
+
11
+ ```ts
12
+ import { defineSecrets } from "@agentproto/secrets"
13
+
14
+ const x = defineSecrets({
15
+ id: "my-secrets",
16
+ description: "Short purpose.",
17
+ // ...
18
+ })
19
+ ```
20
+
21
+ ## License
22
+
23
+ MIT — see [LICENSE](./LICENSE).
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env node
2
+ // Thin launcher — the built CLI runs on import. Kept separate so the shebang
3
+ // lives on a checked-in file rather than fighting the bundler's per-entry output.
4
+ import "../dist/cli.mjs"
@@ -0,0 +1,132 @@
1
+ import { generateKeyPairSync, createPrivateKey, createPublicKey, createHash, diffieHellman, randomBytes, createCipheriv, createDecipheriv, hkdfSync } from 'crypto';
2
+
3
+ /**
4
+ * @agentproto/secrets v0.1.0-alpha
5
+ * AIP-19 SECRETS.md `defineSecrets` reference implementation.
6
+ */
7
+
8
+ var SEAL_ALG = "x25519-hkdf-sha256-aes256gcm";
9
+ var SEAL_VERSION = 1;
10
+ var HKDF_INFO = "agentproto/secrets/seal v1";
11
+ var SealError = class extends Error {
12
+ constructor(message) {
13
+ super(message);
14
+ this.name = "SealError";
15
+ }
16
+ };
17
+ function generateSealKeyPair() {
18
+ const { publicKey, privateKey } = generateKeyPairSync("x25519");
19
+ return {
20
+ publicKey: publicKey.export({ type: "spki", format: "der" }).toString("base64"),
21
+ privateKey: privateKey.export({ type: "pkcs8", format: "der" }).toString("base64")
22
+ };
23
+ }
24
+ function sealingPublicKey(privateKey) {
25
+ let priv;
26
+ try {
27
+ priv = createPrivateKey({
28
+ key: Buffer.from(privateKey, "base64"),
29
+ format: "der",
30
+ type: "pkcs8"
31
+ });
32
+ } catch {
33
+ throw new SealError("invalid private key");
34
+ }
35
+ return createPublicKey(priv).export({ type: "spki", format: "der" }).toString("base64");
36
+ }
37
+ function sealKeyId(publicKey) {
38
+ return createHash("sha256").update(Buffer.from(publicKey, "base64")).digest("hex").slice(0, 16);
39
+ }
40
+ function deriveKey(shared, epkDer, rpkDer) {
41
+ const salt = Buffer.concat([epkDer, rpkDer]);
42
+ return Buffer.from(hkdfSync("sha256", shared, salt, HKDF_INFO, 32));
43
+ }
44
+ function seal(plaintext, recipientPublicKey) {
45
+ let recipient;
46
+ try {
47
+ recipient = createPublicKey({
48
+ key: Buffer.from(recipientPublicKey, "base64"),
49
+ format: "der",
50
+ type: "spki"
51
+ });
52
+ } catch {
53
+ throw new SealError("invalid recipient public key");
54
+ }
55
+ const ephemeral = generateKeyPairSync("x25519");
56
+ const shared = diffieHellman({
57
+ privateKey: ephemeral.privateKey,
58
+ publicKey: recipient
59
+ });
60
+ const epkDer = ephemeral.publicKey.export({ type: "spki", format: "der" });
61
+ const rpkDer = recipient.export({ type: "spki", format: "der" });
62
+ const key = deriveKey(shared, epkDer, rpkDer);
63
+ const iv = randomBytes(12);
64
+ const cipher = createCipheriv("aes-256-gcm", key, iv);
65
+ const pt = typeof plaintext === "string" ? Buffer.from(plaintext, "utf8") : Buffer.from(plaintext);
66
+ const ct = Buffer.concat([cipher.update(pt), cipher.final()]);
67
+ const tag = cipher.getAuthTag();
68
+ const envelope = {
69
+ v: SEAL_VERSION,
70
+ alg: SEAL_ALG,
71
+ epk: epkDer.toString("base64"),
72
+ iv: iv.toString("base64"),
73
+ ct: ct.toString("base64"),
74
+ tag: tag.toString("base64")
75
+ };
76
+ return Buffer.from(JSON.stringify(envelope), "utf8").toString("base64");
77
+ }
78
+ function unseal(sealed, privateKey) {
79
+ let envelope;
80
+ try {
81
+ envelope = JSON.parse(Buffer.from(sealed, "base64").toString("utf8"));
82
+ } catch {
83
+ throw new SealError("malformed sealed envelope");
84
+ }
85
+ if (envelope.v !== SEAL_VERSION || envelope.alg !== SEAL_ALG) {
86
+ throw new SealError(
87
+ `unsupported sealed envelope (v=${envelope.v} alg=${envelope.alg})`
88
+ );
89
+ }
90
+ let priv;
91
+ try {
92
+ priv = createPrivateKey({
93
+ key: Buffer.from(privateKey, "base64"),
94
+ format: "der",
95
+ type: "pkcs8"
96
+ });
97
+ } catch {
98
+ throw new SealError("invalid private key");
99
+ }
100
+ let ephemeral;
101
+ try {
102
+ ephemeral = createPublicKey({
103
+ key: Buffer.from(envelope.epk, "base64"),
104
+ format: "der",
105
+ type: "spki"
106
+ });
107
+ } catch {
108
+ throw new SealError("malformed ephemeral key in envelope");
109
+ }
110
+ const shared = diffieHellman({ privateKey: priv, publicKey: ephemeral });
111
+ const rpkDer = createPublicKey(priv).export({ type: "spki", format: "der" });
112
+ const key = deriveKey(shared, Buffer.from(envelope.epk, "base64"), rpkDer);
113
+ const decipher = createDecipheriv(
114
+ "aes-256-gcm",
115
+ key,
116
+ Buffer.from(envelope.iv, "base64")
117
+ );
118
+ decipher.setAuthTag(Buffer.from(envelope.tag, "base64"));
119
+ try {
120
+ const pt = Buffer.concat([
121
+ decipher.update(Buffer.from(envelope.ct, "base64")),
122
+ decipher.final()
123
+ ]);
124
+ return pt.toString("utf8");
125
+ } catch {
126
+ throw new SealError("unseal failed \u2014 wrong key or tampered ciphertext");
127
+ }
128
+ }
129
+
130
+ export { SEAL_ALG, SEAL_VERSION, SealError, generateSealKeyPair, seal, sealKeyId, sealingPublicKey, unseal };
131
+ //# sourceMappingURL=chunk-MVHOJPML.mjs.map
132
+ //# sourceMappingURL=chunk-MVHOJPML.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/seal/index.ts"],"names":[],"mappings":"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","file":"chunk-MVHOJPML.mjs","sourcesContent":["/**\n * @agentproto/secrets/seal — anonymous public-key sealing for secret values.\n *\n * A \"sealed box\": encrypt a secret TO a recipient's public key such that only\n * the holder of the matching private key can open it. The sender needs only\n * the public key, and the sealed envelope reveals nothing about the sender.\n *\n * The point is custody. A secret value (a CLI subscription token, an API key)\n * can be sealed on the machine that holds it, then handed to ANY intermediary\n * — a relay, an agent, a log — as opaque ciphertext, because only the server's\n * private key can recover the plaintext. The plaintext never has to exist\n * anywhere but the origin machine and, transiently, inside the recipient's\n * unseal boundary.\n *\n * Construction (ECIES, Node built-ins only — no native dep):\n * - X25519 ephemeral key agreement with the recipient's public key\n * - HKDF-SHA256 over the shared secret, salted by both public keys\n * - AES-256-GCM AEAD for the payload (authenticated; tampering fails closed)\n *\n * This is the same shape as libsodium's `crypto_box_seal`, expressed in terms\n * of Node's `crypto` so the package keeps its zero-native-dependency footprint.\n *\n * Sealing (confidentiality) is NOT signing (authenticity): this hides the\n * value, it does not prove who sent it. Bind the sender separately (e.g. an\n * authenticated transport) when provenance matters.\n */\n\nimport {\n generateKeyPairSync,\n createPublicKey,\n createPrivateKey,\n createHash,\n diffieHellman,\n hkdfSync,\n randomBytes,\n createCipheriv,\n createDecipheriv,\n} from \"node:crypto\"\n\n/** Versioned algorithm tag embedded in every envelope so the format can\n * evolve without ambiguity at the unseal site. */\nexport const SEAL_ALG = \"x25519-hkdf-sha256-aes256gcm\" as const\nexport const SEAL_VERSION = 1 as const\n\nconst HKDF_INFO = \"agentproto/secrets/seal v1\"\n\n/** Raised for every seal/unseal failure. The message is safe to surface —\n * it never contains key material or plaintext. */\nexport class SealError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"SealError\"\n }\n}\n\n/** An X25519 keypair for sealing. Both halves are base64-encoded DER so they\n * travel as plain strings (env vars, JSON, config). The public half is safe\n * to publish; the private half must stay with the unseal boundary. */\nexport interface SealKeyPair {\n /** base64 DER (SPKI) X25519 public key — publishable. */\n publicKey: string\n /** base64 DER (PKCS8) X25519 private key — secret. */\n privateKey: string\n}\n\ninterface SealEnvelope {\n v: number\n alg: string\n /** ephemeral public key, base64 DER (SPKI) */\n epk: string\n /** AES-GCM iv, base64 (12 bytes) */\n iv: string\n /** ciphertext, base64 */\n ct: string\n /** AES-GCM auth tag, base64 (16 bytes) */\n tag: string\n}\n\n/**\n * Mint a fresh sealing keypair. The recipient (e.g. a server) generates this\n * once, stores `privateKey` in its own secret store, and publishes\n * `publicKey` for senders to seal against.\n */\nexport function generateSealKeyPair(): SealKeyPair {\n const { publicKey, privateKey } = generateKeyPairSync(\"x25519\")\n return {\n publicKey: publicKey\n .export({ type: \"spki\", format: \"der\" })\n .toString(\"base64\"),\n privateKey: privateKey\n .export({ type: \"pkcs8\", format: \"der\" })\n .toString(\"base64\"),\n }\n}\n\n/**\n * Derive the publishable public key from a stored private key. Lets a\n * recipient hold only the private half (one secret) and serve the public\n * half on demand — the seal-key a sender needs.\n */\nexport function sealingPublicKey(privateKey: string): string {\n let priv\n try {\n priv = createPrivateKey({\n key: Buffer.from(privateKey, \"base64\"),\n format: \"der\",\n type: \"pkcs8\",\n })\n } catch {\n throw new SealError(\"invalid private key\")\n }\n return createPublicKey(priv)\n .export({ type: \"spki\", format: \"der\" })\n .toString(\"base64\")\n}\n\n/**\n * Stable short identifier for a sealing key, derived from the public key.\n * Lets the seal-key endpoint and the sealed envelope name which key was used\n * so rotation is unambiguous. Not a secret.\n */\nexport function sealKeyId(publicKey: string): string {\n return createHash(\"sha256\")\n .update(Buffer.from(publicKey, \"base64\"))\n .digest(\"hex\")\n .slice(0, 16)\n}\n\n// The key-derivation salt binds the symmetric key to BOTH the ephemeral and\n// the recipient public key (sealed-box style) so a derived key is unique to a\n// single (ephemeral, recipient) pair and can't be transplanted.\nfunction deriveKey(shared: Buffer, epkDer: Buffer, rpkDer: Buffer): Buffer {\n const salt = Buffer.concat([epkDer, rpkDer])\n return Buffer.from(hkdfSync(\"sha256\", shared, salt, HKDF_INFO, 32))\n}\n\n/**\n * Seal a plaintext value to a recipient's public key. Returns a single\n * base64 string (the envelope) that only the matching private key can open.\n * The sender needs nothing but the public key.\n */\nexport function seal(\n plaintext: string | Uint8Array,\n recipientPublicKey: string\n): string {\n let recipient\n try {\n recipient = createPublicKey({\n key: Buffer.from(recipientPublicKey, \"base64\"),\n format: \"der\",\n type: \"spki\",\n })\n } catch {\n throw new SealError(\"invalid recipient public key\")\n }\n const ephemeral = generateKeyPairSync(\"x25519\")\n const shared = diffieHellman({\n privateKey: ephemeral.privateKey,\n publicKey: recipient,\n })\n const epkDer = ephemeral.publicKey.export({ type: \"spki\", format: \"der\" })\n const rpkDer = recipient.export({ type: \"spki\", format: \"der\" })\n const key = deriveKey(shared, epkDer, rpkDer)\n\n const iv = randomBytes(12)\n const cipher = createCipheriv(\"aes-256-gcm\", key, iv)\n const pt =\n typeof plaintext === \"string\"\n ? Buffer.from(plaintext, \"utf8\")\n : Buffer.from(plaintext)\n const ct = Buffer.concat([cipher.update(pt), cipher.final()])\n const tag = cipher.getAuthTag()\n\n const envelope: SealEnvelope = {\n v: SEAL_VERSION,\n alg: SEAL_ALG,\n epk: epkDer.toString(\"base64\"),\n iv: iv.toString(\"base64\"),\n ct: ct.toString(\"base64\"),\n tag: tag.toString(\"base64\"),\n }\n return Buffer.from(JSON.stringify(envelope), \"utf8\").toString(\"base64\")\n}\n\n/**\n * Open a sealed envelope with the recipient's private key, returning the\n * original UTF-8 plaintext. Throws `SealError` on a malformed envelope, an\n * unsupported version/alg, the wrong key, or any tampering (the AEAD tag\n * fails closed — a modified ciphertext never decrypts to garbage, it throws).\n */\nexport function unseal(sealed: string, privateKey: string): string {\n let envelope: SealEnvelope\n try {\n envelope = JSON.parse(Buffer.from(sealed, \"base64\").toString(\"utf8\"))\n } catch {\n throw new SealError(\"malformed sealed envelope\")\n }\n if (envelope.v !== SEAL_VERSION || envelope.alg !== SEAL_ALG) {\n throw new SealError(\n `unsupported sealed envelope (v=${envelope.v} alg=${envelope.alg})`\n )\n }\n\n let priv\n try {\n priv = createPrivateKey({\n key: Buffer.from(privateKey, \"base64\"),\n format: \"der\",\n type: \"pkcs8\",\n })\n } catch {\n throw new SealError(\"invalid private key\")\n }\n let ephemeral\n try {\n ephemeral = createPublicKey({\n key: Buffer.from(envelope.epk, \"base64\"),\n format: \"der\",\n type: \"spki\",\n })\n } catch {\n throw new SealError(\"malformed ephemeral key in envelope\")\n }\n\n const shared = diffieHellman({ privateKey: priv, publicKey: ephemeral })\n const rpkDer = createPublicKey(priv).export({ type: \"spki\", format: \"der\" })\n const key = deriveKey(shared, Buffer.from(envelope.epk, \"base64\"), rpkDer)\n\n const decipher = createDecipheriv(\n \"aes-256-gcm\",\n key,\n Buffer.from(envelope.iv, \"base64\")\n )\n decipher.setAuthTag(Buffer.from(envelope.tag, \"base64\"))\n try {\n const pt = Buffer.concat([\n decipher.update(Buffer.from(envelope.ct, \"base64\")),\n decipher.final(),\n ])\n return pt.toString(\"utf8\")\n } catch {\n throw new SealError(\"unseal failed — wrong key or tampered ciphertext\")\n }\n}\n"]}
@@ -0,0 +1,226 @@
1
+ import { resolveCredential, extractJsonPath } from './chunk-NLZ5HXGO.mjs';
2
+ import { z } from 'zod';
3
+ import { createDoctype } from '@agentproto/define-doctype';
4
+ import matter from 'gray-matter';
5
+
6
+ /**
7
+ * @agentproto/secrets v0.1.0-alpha
8
+ * AIP-19 SECRETS.md `defineSecrets` reference implementation.
9
+ */
10
+ var credentialSourceSpecSchema = z.union([
11
+ z.object({
12
+ file: z.string().min(1),
13
+ jsonPath: z.string().min(1).optional()
14
+ }).strict(),
15
+ z.object({ env: z.string().min(1) }).strict(),
16
+ z.object({
17
+ keychain: z.string().min(1),
18
+ account: z.string().min(1).optional(),
19
+ jsonPath: z.string().min(1).optional()
20
+ }).strict(),
21
+ z.object({ prompt: z.string().min(1) }).strict()
22
+ ]).describe(
23
+ "Where a method's credential is read on the origin machine: a file (optionally a JSON field), an env var, the macOS Keychain, or an interactive prompt. Never a literal value."
24
+ );
25
+ var methodSourceSchema = z.union([
26
+ credentialSourceSpecSchema,
27
+ z.array(credentialSourceSpecSchema).min(1)
28
+ ]);
29
+ var recipeMethodSchema = z.object({
30
+ id: z.string().min(1),
31
+ label: z.string().min(1).optional(),
32
+ source: methodSourceSchema
33
+ }).strict();
34
+ var provisionRecipeFrontmatterSchema = z.object({
35
+ id: z.string().min(2).max(80),
36
+ description: z.string().min(1).max(2e3),
37
+ methods: z.array(recipeMethodSchema).min(1),
38
+ label: z.string().min(1).optional()
39
+ }).strict().describe(
40
+ "A credential-provision recipe: for one provider, the installable flavors (methods) and where each flavor's credential lives locally. Holds locations, never values."
41
+ );
42
+ var defineProvisionRecipe = createDoctype({
43
+ aip: 19,
44
+ name: "provisionRecipe",
45
+ validate(def) {
46
+ const result = provisionRecipeFrontmatterSchema.safeParse(def);
47
+ if (!result.success) {
48
+ throw new Error(
49
+ `defineProvisionRecipe (AIP-19): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
50
+ );
51
+ }
52
+ },
53
+ build(def) {
54
+ return {
55
+ ...def,
56
+ methods: def.methods.map((m) => Object.freeze({ ...m }))
57
+ };
58
+ }
59
+ });
60
+ function parseRecipeManifestRaw(source) {
61
+ const parsed = matter(source);
62
+ if (Object.keys(parsed.data).length === 0) {
63
+ throw new Error("parseRecipeManifest: missing or empty frontmatter");
64
+ }
65
+ const result = provisionRecipeFrontmatterSchema.safeParse(parsed.data);
66
+ if (!result.success) {
67
+ throw new Error(
68
+ `parseRecipeManifest: invalid frontmatter \u2014 ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
69
+ );
70
+ }
71
+ return { frontmatter: result.data, body: parsed.content };
72
+ }
73
+ function parseRecipeManifest(source) {
74
+ const { frontmatter } = parseRecipeManifestRaw(source);
75
+ return defineProvisionRecipe(
76
+ frontmatter
77
+ );
78
+ }
79
+
80
+ // src/provision/recipe/builtins.ts
81
+ var claudeCodeOauthRecipe = defineProvisionRecipe({
82
+ id: "claude-code-oauth",
83
+ description: "Claude Code subscription OAuth access token, read from the macOS Keychain or the local CLI's credential file.",
84
+ label: "Claude Code (subscription)",
85
+ methods: [
86
+ {
87
+ id: "subscription-token",
88
+ source: [
89
+ {
90
+ keychain: "Claude Code-credentials",
91
+ jsonPath: "claudeAiOauth.accessToken"
92
+ },
93
+ {
94
+ file: "~/.claude/.credentials.json",
95
+ jsonPath: "claudeAiOauth.accessToken"
96
+ }
97
+ ]
98
+ }
99
+ ]
100
+ });
101
+ var codexRecipe = defineProvisionRecipe({
102
+ id: "codex",
103
+ description: "OpenAI Codex CLI credential \u2014 either the subscription OAuth access token from the local auth file, or an API key from the environment.",
104
+ label: "Codex",
105
+ methods: [
106
+ {
107
+ id: "oauth-subscription",
108
+ label: "Codex (subscription)",
109
+ source: { file: "~/.codex/auth.json", jsonPath: "tokens.access_token" }
110
+ },
111
+ {
112
+ id: "api-key",
113
+ label: "Codex (API key)",
114
+ source: { env: "OPENAI_API_KEY" }
115
+ }
116
+ ]
117
+ });
118
+ var geminiRecipe = defineProvisionRecipe({
119
+ id: "gemini",
120
+ description: "Google Gemini CLI OAuth access token, read from the local CLI's credential cache.",
121
+ label: "Gemini",
122
+ methods: [
123
+ {
124
+ id: "oauth-token",
125
+ source: {
126
+ file: "~/.gemini/oauth_creds.json",
127
+ jsonPath: "access_token"
128
+ }
129
+ }
130
+ ]
131
+ });
132
+ var BUILTIN_RECIPES = [
133
+ claudeCodeOauthRecipe,
134
+ codexRecipe,
135
+ geminiRecipe
136
+ ];
137
+
138
+ // src/provision/recipe/registry.ts
139
+ var _registry = /* @__PURE__ */ new Map();
140
+ for (const r of BUILTIN_RECIPES) _registry.set(r.id, r);
141
+ function registerRecipe(recipe) {
142
+ _registry.set(recipe.id, recipe);
143
+ }
144
+ function getRecipe(id) {
145
+ return _registry.get(id);
146
+ }
147
+ function listRecipes() {
148
+ return [..._registry.values()];
149
+ }
150
+ function listRecipeIds() {
151
+ return [..._registry.keys()];
152
+ }
153
+ function resolveRecipeMethod(id, methodId) {
154
+ const recipe = getRecipe(id);
155
+ if (!recipe) {
156
+ throw new Error(
157
+ `unknown provider '${id}' \u2014 known: ${listRecipeIds().join(", ") || "(none)"}`
158
+ );
159
+ }
160
+ if (!methodId) {
161
+ return { recipe, method: recipe.methods[0] };
162
+ }
163
+ const method = recipe.methods.find((m) => m.id === methodId);
164
+ if (!method) {
165
+ throw new Error(
166
+ `provider '${id}' has no method '${methodId}' \u2014 methods: ${recipe.methods.map((m) => m.id).join(", ")}`
167
+ );
168
+ }
169
+ return { recipe, method };
170
+ }
171
+
172
+ // src/provision/recipe/resolve.ts
173
+ async function resolveSourceSpec(source, opts = {}) {
174
+ const chain = Array.isArray(source) ? source : [source];
175
+ const errors = [];
176
+ for (const spec of chain) {
177
+ try {
178
+ return await resolveOne(spec, opts);
179
+ } catch (err) {
180
+ errors.push(err instanceof Error ? err.message : String(err));
181
+ }
182
+ }
183
+ throw new Error(`no credential source resolved (${errors.join("; ")})`);
184
+ }
185
+ async function resolveOne(source, opts) {
186
+ if ("env" in source) {
187
+ return resolveCredential({ fromEnv: source.env });
188
+ }
189
+ if ("file" in source) {
190
+ return resolveCredential({
191
+ fromFile: source.file,
192
+ ...source.jsonPath ? { jsonPath: source.jsonPath } : {}
193
+ });
194
+ }
195
+ if ("keychain" in source) {
196
+ return resolveKeychain(source);
197
+ }
198
+ if (!opts.promptImpl) {
199
+ throw new Error(
200
+ `credential source needs an interactive prompt but no promptImpl was provided`
201
+ );
202
+ }
203
+ const value = await opts.promptImpl(source.prompt);
204
+ if (!value) throw new Error("prompt returned an empty credential");
205
+ return value;
206
+ }
207
+ async function resolveKeychain(source) {
208
+ if (process.platform !== "darwin") {
209
+ throw new Error("keychain source is macOS-only");
210
+ }
211
+ const { execFileSync } = await import('child_process');
212
+ const args = ["find-generic-password", "-w", "-s", source.keychain];
213
+ if (source.account) args.push("-a", source.account);
214
+ let raw;
215
+ try {
216
+ raw = execFileSync("security", args, { encoding: "utf8" }).trim();
217
+ } catch {
218
+ throw new Error(`keychain item '${source.keychain}' not found`);
219
+ }
220
+ if (!raw) throw new Error(`keychain item '${source.keychain}' is empty`);
221
+ return source.jsonPath ? extractJsonPath(raw, source.jsonPath) : raw;
222
+ }
223
+
224
+ export { BUILTIN_RECIPES, claudeCodeOauthRecipe, codexRecipe, credentialSourceSpecSchema, defineProvisionRecipe, geminiRecipe, getRecipe, listRecipeIds, listRecipes, parseRecipeManifest, parseRecipeManifestRaw, provisionRecipeFrontmatterSchema, recipeMethodSchema, registerRecipe, resolveRecipeMethod, resolveSourceSpec };
225
+ //# sourceMappingURL=chunk-NEGXQWE5.mjs.map
226
+ //# sourceMappingURL=chunk-NEGXQWE5.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provision/recipe/schema.ts","../src/provision/recipe/define-recipe.ts","../src/provision/recipe/manifest.ts","../src/provision/recipe/builtins.ts","../src/provision/recipe/registry.ts","../src/provision/recipe/resolve.ts"],"names":[],"mappings":"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","file":"chunk-NEGXQWE5.mjs","sourcesContent":["/**\n * Provision-recipe frontmatter / literal zod schema.\n *\n * Single source of truth for both authoring paths: `defineProvisionRecipe`\n * (TS literal) and `parseRecipeManifest` (.md) run this same schema, so a\n * malformed literal and a malformed manifest fail identically — mirrors how\n * `secretsFrontmatterSchema` backs both `defineSecrets` and\n * `parseSecretsManifest`.\n *\n * Hand-written rather than scaffold-aip-generated: a recipe is not yet a\n * numbered-AIP artifact. Promote to a generated schema if it gets its own spec.\n */\n\nimport { z } from \"zod\"\n\n/** Exactly one of file / env / keychain / prompt. `value` is explicitly\n * forbidden — a recipe carries locations, never secrets. */\nexport const credentialSourceSpecSchema = z\n .union([\n z\n .object({\n file: z.string().min(1),\n jsonPath: z.string().min(1).optional(),\n })\n .strict(),\n z.object({ env: z.string().min(1) }).strict(),\n z\n .object({\n keychain: z.string().min(1),\n account: z.string().min(1).optional(),\n jsonPath: z.string().min(1).optional(),\n })\n .strict(),\n z.object({ prompt: z.string().min(1) }).strict(),\n ])\n .describe(\n \"Where a method's credential is read on the origin machine: a file (optionally a JSON field), an env var, the macOS Keychain, or an interactive prompt. Never a literal value.\",\n )\n\n/** A method's source: a single spec or an ordered fallback chain (first that\n * resolves wins). */\nexport const methodSourceSchema = z.union([\n credentialSourceSpecSchema,\n z.array(credentialSourceSpecSchema).min(1),\n])\n\nexport const recipeMethodSchema = z\n .object({\n id: z.string().min(1),\n label: z.string().min(1).optional(),\n source: methodSourceSchema,\n })\n .strict()\n\nexport const provisionRecipeFrontmatterSchema = z\n .object({\n id: z.string().min(2).max(80),\n description: z.string().min(1).max(2000),\n methods: z.array(recipeMethodSchema).min(1),\n label: z.string().min(1).optional(),\n })\n .strict()\n .describe(\n \"A credential-provision recipe: for one provider, the installable flavors (methods) and where each flavor's credential lives locally. Holds locations, never values.\",\n )\n\nexport type ProvisionRecipeFrontmatter = z.infer<\n typeof provisionRecipeFrontmatterSchema\n>\n","/**\n * `defineProvisionRecipe` — the TS-literal authoring path for a recipe.\n *\n * Built on `createDoctype` so a recipe gets the same cross-AIP invariants as\n * every other doctype: id-pattern check, ≤2000-char description, frozen handle,\n * canonical \"defineProvisionRecipe (AIP-19): …\" error prefix. Field-level\n * validation runs the shared zod from `./schema.ts`, so a malformed literal\n * fails with the same diagnostic as a malformed `.md`.\n */\n\nimport { createDoctype } from \"@agentproto/define-doctype\"\nimport { provisionRecipeFrontmatterSchema } from \"./schema.js\"\nimport type {\n ProvisionRecipe,\n ProvisionRecipeDefinition,\n} from \"./types.js\"\n\nexport const defineProvisionRecipe = createDoctype<\n ProvisionRecipeDefinition,\n ProvisionRecipe\n>({\n aip: 19,\n name: \"provisionRecipe\",\n validate(def) {\n const result = provisionRecipeFrontmatterSchema.safeParse(def)\n if (!result.success) {\n throw new Error(\n `defineProvisionRecipe (AIP-19): ${result.error.issues\n .map((i) => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \")}`,\n )\n }\n },\n build(def) {\n return {\n ...def,\n methods: def.methods.map((m) => Object.freeze({ ...m })) as ProvisionRecipe[\"methods\"],\n }\n },\n})\n","/**\n * `.md` authoring path for a provision recipe — parse a frontmatter manifest\n * into a frozen handle. Mirrors `parseSecretsManifest`: gray-matter splits the\n * frontmatter, the shared zod validates it, `defineProvisionRecipe` builds the\n * handle so the `.md` and TS-literal paths converge on one validated shape.\n *\n * The package ships its builtin recipes as TS literals (they bundle with zero\n * fs); this parser is for a HOST that reads its own external `.md` recipes and\n * registers them — the extension seam.\n */\n\nimport matter from \"gray-matter\"\nimport {\n provisionRecipeFrontmatterSchema,\n type ProvisionRecipeFrontmatter,\n} from \"./schema.js\"\nimport { defineProvisionRecipe } from \"./define-recipe.js\"\nimport type { ProvisionRecipe, ProvisionRecipeDefinition } from \"./types.js\"\n\nexport interface ProvisionRecipeManifest {\n frontmatter: ProvisionRecipeFrontmatter\n body: string\n}\n\nexport function parseRecipeManifestRaw(\n source: string,\n): ProvisionRecipeManifest {\n const parsed = matter(source)\n if (Object.keys(parsed.data).length === 0) {\n throw new Error(\"parseRecipeManifest: missing or empty frontmatter\")\n }\n const result = provisionRecipeFrontmatterSchema.safeParse(parsed.data)\n if (!result.success) {\n throw new Error(\n `parseRecipeManifest: invalid frontmatter — ${result.error.issues\n .map((i) => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \")}`,\n )\n }\n return { frontmatter: result.data, body: parsed.content }\n}\n\n/** Parse a recipe `.md` straight to a frozen handle (frontmatter →\n * `defineProvisionRecipe`). */\nexport function parseRecipeManifest(source: string): ProvisionRecipe {\n const { frontmatter } = parseRecipeManifestRaw(source)\n return defineProvisionRecipe(\n frontmatter as unknown as ProvisionRecipeDefinition,\n )\n}\n","/**\n * Builtin provision recipes — the credential providers the flow knows out of\n * the box. Shipped as TS literals (not `.md`) so they bundle into the tsup\n * output with zero filesystem read at runtime. A host adds more by parsing its\n * own `.md` with `parseRecipeManifest` and `registerRecipe`-ing the result.\n *\n * Each recipe describes the credential's PUBLIC local convention for that CLI\n * (where it writes its token) — no vendor's private infra, no values.\n */\n\nimport { defineProvisionRecipe } from \"./define-recipe.js\"\nimport type { ProvisionRecipe } from \"./types.js\"\n\n/** Claude Code subscription OAuth token. macOS stores it in the login Keychain;\n * Linux writes it to ~/.claude/.credentials.json. Try the Keychain first, fall\n * back to the file, so one recipe spans both. */\nexport const claudeCodeOauthRecipe = defineProvisionRecipe({\n id: \"claude-code-oauth\",\n description:\n \"Claude Code subscription OAuth access token, read from the macOS Keychain or the local CLI's credential file.\",\n label: \"Claude Code (subscription)\",\n methods: [\n {\n id: \"subscription-token\",\n source: [\n {\n keychain: \"Claude Code-credentials\",\n jsonPath: \"claudeAiOauth.accessToken\",\n },\n {\n file: \"~/.claude/.credentials.json\",\n jsonPath: \"claudeAiOauth.accessToken\",\n },\n ],\n },\n ],\n})\n\n/** Codex CLI — two flavors: the subscription OAuth token, or a raw API key. */\nexport const codexRecipe = defineProvisionRecipe({\n id: \"codex\",\n description:\n \"OpenAI Codex CLI credential — either the subscription OAuth access token from the local auth file, or an API key from the environment.\",\n label: \"Codex\",\n methods: [\n {\n id: \"oauth-subscription\",\n label: \"Codex (subscription)\",\n source: { file: \"~/.codex/auth.json\", jsonPath: \"tokens.access_token\" },\n },\n {\n id: \"api-key\",\n label: \"Codex (API key)\",\n source: { env: \"OPENAI_API_KEY\" },\n },\n ],\n})\n\n/** Gemini CLI OAuth token written by the local CLI. */\nexport const geminiRecipe = defineProvisionRecipe({\n id: \"gemini\",\n description:\n \"Google Gemini CLI OAuth access token, read from the local CLI's credential cache.\",\n label: \"Gemini\",\n methods: [\n {\n id: \"oauth-token\",\n source: {\n file: \"~/.gemini/oauth_creds.json\",\n jsonPath: \"access_token\",\n },\n },\n ],\n})\n\nexport const BUILTIN_RECIPES: readonly ProvisionRecipe[] = [\n claudeCodeOauthRecipe,\n codexRecipe,\n geminiRecipe,\n]\n","/**\n * Provision-recipe registry — id → recipe lookup, pre-seeded with the builtins.\n *\n * Mirrors the host-side OAuth-provider registry shape: a module-level Map a\n * caller resolves by id at provision time, plus a `registerRecipe` seam so a\n * host can add recipes parsed from its own `.md` (`parseRecipeManifest`)\n * without editing this package. Re-registering an id overrides it — last write\n * wins, so a host can shadow a builtin.\n */\n\nimport { BUILTIN_RECIPES } from \"./builtins.js\"\nimport type { ProvisionRecipe, RecipeMethod } from \"./types.js\"\n\nconst _registry = new Map<string, ProvisionRecipe>()\nfor (const r of BUILTIN_RECIPES) _registry.set(r.id, r)\n\nexport function registerRecipe(recipe: ProvisionRecipe): void {\n _registry.set(recipe.id, recipe)\n}\n\nexport function getRecipe(id: string): ProvisionRecipe | undefined {\n return _registry.get(id)\n}\n\nexport function listRecipes(): ProvisionRecipe[] {\n return [..._registry.values()]\n}\n\nexport function listRecipeIds(): string[] {\n return [..._registry.keys()]\n}\n\n/**\n * Resolve a recipe + one of its methods. `methodId` selects among the recipe's\n * flavors; omitted picks the first (default). Throws a caller-friendly error\n * naming the available methods when the id is unknown or ambiguous.\n */\nexport function resolveRecipeMethod(\n id: string,\n methodId?: string,\n): { recipe: ProvisionRecipe; method: RecipeMethod } {\n const recipe = getRecipe(id)\n if (!recipe) {\n throw new Error(\n `unknown provider '${id}' — known: ${listRecipeIds().join(\", \") || \"(none)\"}`,\n )\n }\n if (!methodId) {\n return { recipe, method: recipe.methods[0] }\n }\n const method = recipe.methods.find((m) => m.id === methodId)\n if (!method) {\n throw new Error(\n `provider '${id}' has no method '${methodId}' — methods: ${recipe.methods\n .map((m) => m.id)\n .join(\", \")}`,\n )\n }\n return { recipe, method }\n}\n","/**\n * Resolve a recipe method's source to plaintext.\n *\n * A source is a single `CredentialSourceSpec` or an ordered fallback chain\n * (array) — the first that resolves wins, so one recipe spans platforms\n * (macOS Keychain → file). Bridges onto `resolveCredential` (file / env), the\n * macOS `security` Keychain, and an injected prompt impl (interactive, e.g. a\n * website password for a remote browser). The prompt impl is INJECTED — no\n * hard TTY dependency at import, same discipline as `fetchImpl` on\n * `httpTarget`. The result is sensitive: seal it immediately, never print it.\n */\n\nimport { resolveCredential, extractJsonPath } from \"../index.js\"\nimport type { CredentialSourceSpec } from \"./types.js\"\n\nexport interface ResolveSourceOptions {\n /** Asked for a `{ prompt }` source. Receives the prompt text, returns the\n * entered value. Required only when a method uses a prompt source. */\n promptImpl?: (prompt: string) => Promise<string>\n}\n\nexport async function resolveSourceSpec(\n source: CredentialSourceSpec | CredentialSourceSpec[],\n opts: ResolveSourceOptions = {},\n): Promise<string> {\n const chain = Array.isArray(source) ? source : [source]\n const errors: string[] = []\n for (const spec of chain) {\n try {\n return await resolveOne(spec, opts)\n } catch (err) {\n errors.push(err instanceof Error ? err.message : String(err))\n }\n }\n throw new Error(`no credential source resolved (${errors.join(\"; \")})`)\n}\n\nasync function resolveOne(\n source: CredentialSourceSpec,\n opts: ResolveSourceOptions,\n): Promise<string> {\n if (\"env\" in source) {\n return resolveCredential({ fromEnv: source.env })\n }\n if (\"file\" in source) {\n return resolveCredential({\n fromFile: source.file,\n ...(source.jsonPath ? { jsonPath: source.jsonPath } : {}),\n })\n }\n if (\"keychain\" in source) {\n return resolveKeychain(source)\n }\n // prompt\n if (!opts.promptImpl) {\n throw new Error(\n `credential source needs an interactive prompt but no promptImpl was provided`,\n )\n }\n const value = await opts.promptImpl(source.prompt)\n if (!value) throw new Error(\"prompt returned an empty credential\")\n return value\n}\n\nasync function resolveKeychain(source: {\n keychain: string\n account?: string\n jsonPath?: string\n}): Promise<string> {\n if (process.platform !== \"darwin\") {\n throw new Error(\"keychain source is macOS-only\")\n }\n const { execFileSync } = await import(\"node:child_process\")\n const args = [\"find-generic-password\", \"-w\", \"-s\", source.keychain]\n if (source.account) args.push(\"-a\", source.account)\n let raw: string\n try {\n raw = execFileSync(\"security\", args, { encoding: \"utf8\" }).trim()\n } catch {\n throw new Error(`keychain item '${source.keychain}' not found`)\n }\n if (!raw) throw new Error(`keychain item '${source.keychain}' is empty`)\n return source.jsonPath ? extractJsonPath(raw, source.jsonPath) : raw\n}\n"]}
@@ -0,0 +1,90 @@
1
+ import { seal } from './chunk-MVHOJPML.mjs';
2
+
3
+ /**
4
+ * @agentproto/secrets v0.1.0-alpha
5
+ * AIP-19 SECRETS.md `defineSecrets` reference implementation.
6
+ */
7
+
8
+ // src/provision/index.ts
9
+ async function provisionSealed(input) {
10
+ const key = await input.target.fetchSealKey();
11
+ const sealedValue = seal(input.credential, key.publicKey);
12
+ const result = await input.target.installSealed({
13
+ provider: input.provider,
14
+ methodId: input.methodId,
15
+ value: sealedValue,
16
+ keyId: key.keyId,
17
+ ...input.label ? { label: input.label } : {}
18
+ });
19
+ return { ...result, keyId: key.keyId };
20
+ }
21
+ function httpTarget(config) {
22
+ const doFetch = config.fetchImpl ?? fetch;
23
+ return {
24
+ async fetchSealKey() {
25
+ const res = await doFetch(config.sealKeyUrl, {
26
+ headers: config.headers
27
+ });
28
+ if (res.status === 404) {
29
+ throw new Error("target has no sealing key configured");
30
+ }
31
+ if (!res.ok) {
32
+ throw new Error(`seal-key fetch failed: HTTP ${res.status}`);
33
+ }
34
+ return await res.json();
35
+ },
36
+ async installSealed(installInput) {
37
+ const res = await doFetch(config.installUrl, {
38
+ method: "POST",
39
+ headers: { ...config.headers, "content-type": "application/json" },
40
+ body: JSON.stringify({ ...installInput, sealed: true })
41
+ });
42
+ if (!res.ok) {
43
+ const text = await res.text().catch(() => "");
44
+ throw new Error(`install failed: HTTP ${res.status} ${text}`);
45
+ }
46
+ return await res.json();
47
+ }
48
+ };
49
+ }
50
+ function expandHome(p) {
51
+ if (p === "~") return homeDir();
52
+ if (p.startsWith("~/")) return joinPath(homeDir(), p.slice(2));
53
+ return p;
54
+ }
55
+ function homeDir() {
56
+ return process.env.HOME ?? process.env.USERPROFILE ?? "~";
57
+ }
58
+ function joinPath(a, b) {
59
+ return a.replace(/\/$/, "") + "/" + b.replace(/^\//, "");
60
+ }
61
+ function extractJsonPath(raw, path) {
62
+ let cur = JSON.parse(raw);
63
+ for (const part of path.split(".")) {
64
+ if (cur == null || typeof cur !== "object") {
65
+ throw new Error(`credential field '${path}' not found in file`);
66
+ }
67
+ cur = cur[part];
68
+ }
69
+ if (typeof cur !== "string" || cur.length === 0) {
70
+ throw new Error(`credential field '${path}' is not a non-empty string`);
71
+ }
72
+ return cur;
73
+ }
74
+ async function resolveCredential(source) {
75
+ if (source.fromEnv) {
76
+ const v = process.env[source.fromEnv];
77
+ if (!v) throw new Error(`env var ${source.fromEnv} is empty / unset`);
78
+ return v;
79
+ }
80
+ if (!source.fromFile) {
81
+ throw new Error("no credential source \u2014 set fromFile or fromEnv");
82
+ }
83
+ const { readFileSync } = await import('fs');
84
+ const raw = readFileSync(expandHome(source.fromFile), "utf8");
85
+ return source.jsonPath ? extractJsonPath(raw, source.jsonPath) : raw.trim();
86
+ }
87
+
88
+ export { extractJsonPath, httpTarget, provisionSealed, resolveCredential };
89
+ //# sourceMappingURL=chunk-NLZ5HXGO.mjs.map
90
+ //# sourceMappingURL=chunk-NLZ5HXGO.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/provision/index.ts"],"names":[],"mappings":"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","file":"chunk-NLZ5HXGO.mjs","sourcesContent":["/**\n * @agentproto/secrets/provision — seal a secret and install it into a remote\n * vault, carrying only ciphertext.\n *\n * The flow is: resolve a credential → fetch the target's sealing public key →\n * seal to it → hand the sealed blob to the target's install. The plaintext\n * exists only on the origin machine and, transiently, inside the target's\n * unseal boundary — never in whatever drives this (an agent, a relay, a log).\n *\n * The target is an INJECTED PORT: this module knows nothing about any specific\n * server. A caller provides a `SealedInstallTarget` — `httpTarget()` builds a\n * generic one from a seal-key URL + install URL + headers; a host-specific\n * caller (e.g. a product that knows its own server's paths and auth) injects\n * its own. No vendor names leak into this package.\n */\n\nimport { seal } from \"../seal/index.js\"\n\n/** Public sealing material a target advertises so a sender can seal to it. */\nexport interface SealKeyInfo {\n keyId: string\n alg: string\n publicKey: string\n}\n\n/** The sealed payload handed to a target's install. `value` is already a\n * sealed envelope (opaque); `keyId` lets the target reject a value sealed\n * against a rotated key. */\nexport interface SealedInstallInput {\n provider: string\n methodId: string\n value: string\n keyId: string\n label?: string\n}\n\n/**\n * A server that can receive a sealed credential. The two halves a provision\n * needs: advertise the sealing key, and accept the sealed install. Implement\n * this for any concrete server; the provision flow is agnostic to how.\n */\nexport interface SealedInstallTarget {\n fetchSealKey(): Promise<SealKeyInfo>\n installSealed(input: SealedInstallInput): Promise<{ secretId?: string }>\n}\n\nexport interface ProvisionSealedInput {\n target: SealedInstallTarget\n provider: string\n methodId: string\n /** The plaintext credential. Resolve it (e.g. via `resolveCredential`) right\n * before calling so it lives as briefly as possible; it is sealed here and\n * never returned or logged. */\n credential: string\n label?: string\n}\n\n/**\n * Seal `credential` to the target's current key and install it. Returns the\n * target's result plus the keyId used. The plaintext is consumed here and\n * never leaves.\n */\nexport async function provisionSealed(\n input: ProvisionSealedInput\n): Promise<{ secretId?: string; keyId: string }> {\n const key = await input.target.fetchSealKey()\n const sealedValue = seal(input.credential, key.publicKey)\n const result = await input.target.installSealed({\n provider: input.provider,\n methodId: input.methodId,\n value: sealedValue,\n keyId: key.keyId,\n ...(input.label ? { label: input.label } : {}),\n })\n return { ...result, keyId: key.keyId }\n}\n\n/**\n * Build a generic HTTP target from a seal-key URL, an install URL, and\n * optional headers (auth). Vendor-neutral — the URLs and headers are the\n * caller's to supply. The install POSTs `{ ...input, sealed: true }` as JSON.\n */\nexport function httpTarget(config: {\n sealKeyUrl: string\n installUrl: string\n headers?: Record<string, string>\n /** Injected for tests; defaults to global fetch. */\n fetchImpl?: typeof fetch\n}): SealedInstallTarget {\n const doFetch = config.fetchImpl ?? fetch\n return {\n async fetchSealKey() {\n const res = await doFetch(config.sealKeyUrl, {\n headers: config.headers,\n })\n if (res.status === 404) {\n throw new Error(\"target has no sealing key configured\")\n }\n if (!res.ok) {\n throw new Error(`seal-key fetch failed: HTTP ${res.status}`)\n }\n return (await res.json()) as SealKeyInfo\n },\n async installSealed(installInput) {\n const res = await doFetch(config.installUrl, {\n method: \"POST\",\n headers: { ...config.headers, \"content-type\": \"application/json\" },\n body: JSON.stringify({ ...installInput, sealed: true }),\n })\n if (!res.ok) {\n const text = await res.text().catch(() => \"\")\n throw new Error(`install failed: HTTP ${res.status} ${text}`)\n }\n return (await res.json()) as { secretId?: string }\n },\n }\n}\n\n// ── Credential resolution (local sources) ──────────────────────────────\n// A small convenience for CLI / origin-machine callers. Reads a credential\n// from an env var or a file (optionally extracting a JSON field). The result\n// is sensitive — seal it immediately, never print it.\n\nexport interface CredentialSource {\n /** Read from this process env var. */\n fromEnv?: string\n /** Read from this file path (`~` expands to the home dir). */\n fromFile?: string\n /** Dot-path to extract a string from the file's JSON (else whole file). */\n jsonPath?: string\n}\n\nfunction expandHome(p: string): string {\n if (p === \"~\") return homeDir()\n if (p.startsWith(\"~/\")) return joinPath(homeDir(), p.slice(2))\n return p\n}\n\n// Lazily reach for node built-ins so the module stays importable in any\n// runtime; resolveCredential is only called by origin-machine callers.\nfunction homeDir(): string {\n return (\n process.env.HOME ?? process.env.USERPROFILE ?? \"~\"\n )\n}\nfunction joinPath(a: string, b: string): string {\n return a.replace(/\\/$/, \"\") + \"/\" + b.replace(/^\\//, \"\")\n}\n\nexport function extractJsonPath(raw: string, path: string): string {\n let cur: unknown = JSON.parse(raw)\n for (const part of path.split(\".\")) {\n if (cur == null || typeof cur !== \"object\") {\n throw new Error(`credential field '${path}' not found in file`)\n }\n cur = (cur as Record<string, unknown>)[part]\n }\n if (typeof cur !== \"string\" || cur.length === 0) {\n throw new Error(`credential field '${path}' is not a non-empty string`)\n }\n return cur\n}\n\n/**\n * Resolve a plaintext credential from a local source. Sensitive — callers\n * seal the result immediately and never print it. Uses a dynamic `fs` import\n * so the module has no hard Node filesystem dependency at import time.\n */\nexport async function resolveCredential(\n source: CredentialSource\n): Promise<string> {\n if (source.fromEnv) {\n const v = process.env[source.fromEnv]\n if (!v) throw new Error(`env var ${source.fromEnv} is empty / unset`)\n return v\n }\n if (!source.fromFile) {\n throw new Error(\"no credential source — set fromFile or fromEnv\")\n }\n const { readFileSync } = await import(\"node:fs\")\n const raw = readFileSync(expandHome(source.fromFile), \"utf8\")\n return source.jsonPath ? extractJsonPath(raw, source.jsonPath) : raw.trim()\n}\n"]}
@@ -0,0 +1,29 @@
1
+ import { z } from 'zod';
2
+ import { createDoctype } from '@agentproto/define-doctype';
3
+
4
+ /**
5
+ * @agentproto/secrets v0.1.0-alpha
6
+ * AIP-19 SECRETS.md `defineSecrets` reference implementation.
7
+ */
8
+
9
+ var secretsFrontmatterSchema = z.object({ "secrets": z.array(z.any()).min(1) }).strict().describe("Validates the YAML frontmatter portion of an AIP-19 SECRETS.md manifest. The manifest declares secret slugs + access policy + audit metadata. Values are NEVER stored in the manifest.");
10
+ var defineSecrets = createDoctype({
11
+ aip: 19,
12
+ name: "secrets",
13
+ readDescription: false,
14
+ validate(def) {
15
+ const result = secretsFrontmatterSchema.safeParse(def);
16
+ if (!result.success) {
17
+ throw new Error(
18
+ `defineSecrets (AIP-19): ${result.error.issues.map((i) => `${i.path.join(".")}: ${i.message}`).join("; ")}`
19
+ );
20
+ }
21
+ },
22
+ build(def) {
23
+ return { ...def };
24
+ }
25
+ });
26
+
27
+ export { defineSecrets, secretsFrontmatterSchema };
28
+ //# sourceMappingURL=chunk-SFLTQZQB.mjs.map
29
+ //# sourceMappingURL=chunk-SFLTQZQB.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/schema.ts","../src/define-secrets.ts"],"names":[],"mappings":";;;;;;;;AAeO,IAAM,2BAA2B,CAAA,CAAE,MAAA,CAAO,EAAE,SAAA,EAAW,CAAA,CAAE,MAAM,CAAA,CAAE,GAAA,EAAK,CAAA,CAAE,GAAA,CAAI,CAAC,CAAA,EAAG,EAAE,MAAA,EAAO,CAAE,SAAS,wLAAwL;ACM5R,IAAM,gBAAgB,aAAA,CAAgD;AAAA,EAC3E,GAAA,EAAK,EAAA;AAAA,EACL,IAAA,EAAM,SAAA;AAAA,EACN,eAAA,EAAiB,KAAA;AAAA,EACjB,SAAS,GAAA,EAAK;AACZ,IAAA,MAAM,MAAA,GAAS,wBAAA,CAAyB,SAAA,CAAU,GAAG,CAAA;AACrD,IAAA,IAAI,CAAC,OAAO,OAAA,EAAS;AACnB,MAAA,MAAM,IAAI,KAAA;AAAA,QACR,CAAA,wBAAA,EAA2B,OAAO,KAAA,CAAM,MAAA,CACrC,IAAI,CAAC,CAAA,KAAM,GAAG,CAAA,CAAE,IAAA,CAAK,KAAK,GAAG,CAAC,KAAK,CAAA,CAAE,OAAO,EAAE,CAAA,CAC9C,IAAA,CAAK,IAAI,CAAC,CAAA;AAAA,OACf;AAAA,IACF;AAAA,EAIF,CAAA;AAAA,EACA,MAAM,GAAA,EAAK;AAKT,IAAA,OAAO,EAAE,GAAG,GAAA,EAAI;AAAA,EAClB;AACF,CAAC","file":"chunk-SFLTQZQB.mjs","sourcesContent":["/**\n * AIP-19 SECRETS.md frontmatter zod schema.\n *\n * Generated from `resources/aip-19/draft/SECRETS.schema.json` via\n * json-schema-to-zod. Imported by both `define-secrets.ts` (TS path\n * validation) and `manifest/index.ts` (.md path validation) so every\n * field-level constraint runs in both authoring paths from a single\n * source of truth — re-run scaffold-aip to refresh after spec changes.\n *\n * Cross-field rules (if/then/allOf in JSON Schema) don't translate\n * cleanly and live in `define-secrets.ts`'s `validate(def)` instead.\n */\n\nimport { z } from \"zod\"\n\nexport const secretsFrontmatterSchema = z.object({ \"secrets\": z.array(z.any()).min(1) }).strict().describe(\"Validates the YAML frontmatter portion of an AIP-19 SECRETS.md manifest. The manifest declares secret slugs + access policy + audit metadata. Values are NEVER stored in the manifest.\")\n\nexport type SecretsFrontmatter = z.infer<typeof secretsFrontmatterSchema>\n","import { createDoctype } from \"@agentproto/define-doctype\"\nimport { secretsFrontmatterSchema } from \"./schema.js\"\nimport type { SecretsDefinition, SecretsHandle } from \"./types.js\"\n\n/**\n * AIP-19 reference implementation of `defineSecrets`.\n *\n * Built on `createDoctype` so the cross-AIP invariants (id pattern,\n * description length, top-level freeze, \"defineSecrets (AIP-19): …\"\n * error prefix) run uniformly with every other AIP defineX.\n *\n * Field-level validation runs the schema-derived zod from\n * `./schema.ts` against the input. Same source of truth as the .md\n * path uses (`parseSecretsManifest`), so a malformed TS-authored\n * definition fails with the same diagnostic as a malformed manifest.\n * Cross-field rules go in `validate(def)` after the zod check.\n *\n * Identity / description extractors detected from the JSON Schema:\n * readIdentity: def.id\n * readDescription: skipped (no string-y required field detected).\n */\nexport const defineSecrets = createDoctype<SecretsDefinition, SecretsHandle>({\n aip: 19,\n name: \"secrets\",\n readDescription: false,\n validate(def) {\n const result = secretsFrontmatterSchema.safeParse(def)\n if (!result.success) {\n throw new Error(\n `defineSecrets (AIP-19): ${result.error.issues\n .map((i) => `${i.path.join(\".\")}: ${i.message}`)\n .join(\"; \")}`,\n )\n }\n // TODO: spec-19-specific cross-field rules (if/then/allOf in\n // the JSON Schema) — those don't translate to zod cleanly and\n // belong here. See @agentproto/operator's autonomy=gated rule.\n },\n build(def) {\n // Default build: spread the validated definition into a fresh object.\n // Hand-tune for nested freezing (Object.freeze on arrays/objects) and\n // for fields that need defaults applied — see @agentproto/operator\n // for a reference shape.\n return { ...def } as SecretsHandle\n },\n})\n"]}
package/dist/cli.d.ts ADDED
@@ -0,0 +1,2 @@
1
+
2
+ export { }