@seekrit/cli 0.24.0 → 0.26.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.
@@ -1,4 +1,4 @@
1
- import { A as generateMysqlCredential, C as generateSigningKeyMaterial, D as signatureKeyRef, E as signMessage, F as kmsEncrypt, I as wrapDek, L as generateDek, M as generateEncryptKeyMaterial, N as kmsBlobKeyRef, O as verifyMessage, P as kmsDecrypt, R as toBase64, S as parseServiceToken, T as importVerifyingKey, _ as setFailThrows, a as ensureM2mAdminToken, b as createServiceToken, c as kmsResolveKey, d as resolveEnvTarget, f as resolveGroup, g as tryBuildContext, h as isTokenAuth, i as materializeEnv, j as generateDataKey, k as generatePostgresCredential, l as kmsResolveRecipient, m as getDek, n as fetchDecryptedSecrets, o as kmsCallerIdentity, p as resolveOrg, r as fetchDecryptedVersion, s as kmsRecoverMaterial, t as encryptAndSetSecret, u as resolveAppEnv, v as writeProjectConfig, w as importSigningKey, x as isServiceToken, y as version } from "./index.js";
1
+ import { A as verifyMessage, B as toBase64, C as isServiceToken, D as importVerifyingKey, E as importSigningKey, F as kmsBlobKeyRef, I as kmsDecrypt, L as kmsEncrypt, M as generateMysqlCredential, N as generateDataKey, O as signMessage, P as generateEncryptKeyMaterial, R as wrapDek, S as createServiceToken, T as generateSigningKeyMaterial, V as parseBranchTtl, _ as isTokenAuth, a as ensureM2mAdminToken, b as writeProjectConfig, c as kmsResolveKey, d as resolveAppEnv, f as resolveBranch, g as getDek, h as resolveOrg, i as materializeEnv, j as generatePostgresCredential, k as signatureKeyRef, l as kmsResolveRecipient, m as resolveGroup, n as fetchDecryptedSecrets, o as kmsCallerIdentity, p as resolveEnvTarget, r as fetchDecryptedVersion, s as kmsRecoverMaterial, t as encryptAndSetSecret, u as resolveApp, v as tryBuildContext, w as parseServiceToken, x as version, y as setFailThrows, z as generateDek } from "./index.js";
2
2
  import { spawn } from "node:child_process";
3
3
  import { z } from "zod";
4
4
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
@@ -159,6 +159,7 @@ async function materializeFor(ctx, o) {
159
159
  if (!isTokenAuth(ctx)) envId = (await resolveAppEnv(ctx, o)).envId;
160
160
  return materializeEnv(ctx, {
161
161
  envId,
162
+ branch: o.branch,
162
163
  with: o.with,
163
164
  envFiles: o.envFile ?? [".env"]
164
165
  });
@@ -252,6 +253,24 @@ async function runMcpServer(options = {}) {
252
253
  if (!appRow) throw new Error(`no app "${app}" in ${orgRef.slug}`);
253
254
  return (await ctx.client.listEnvs(orgRef.id, appRow.id)).environments;
254
255
  });
256
+ tool("list_branches", "List ephemeral branch configs in an application (optionally just one environment's).", {
257
+ org: z.string().optional(),
258
+ app: z.string(),
259
+ env: z.string().optional()
260
+ }, async ({ org, app, env }) => {
261
+ const ctx = getCtx();
262
+ const appRef = await resolveApp(ctx, {
263
+ org,
264
+ app
265
+ });
266
+ if (!env) return (await ctx.client.listAppBranches(appRef.orgId, appRef.id)).branches;
267
+ const parent = await resolveAppEnv(ctx, {
268
+ org,
269
+ app,
270
+ env
271
+ });
272
+ return (await ctx.client.listBranches(parent.orgId, parent.envId)).branches;
273
+ });
255
274
  tool("list_groups", "List shared groups (reusable secret bags) in an organization.", { org: z.string().optional() }, async ({ org }) => {
256
275
  const ctx = getCtx();
257
276
  const orgRef = await resolveOrg(ctx, org);
@@ -514,6 +533,55 @@ async function runMcpServer(options = {}) {
514
533
  wrappedDek
515
534
  })).environment;
516
535
  });
536
+ tool("create_branch", "Fork an environment into an ephemeral branch (a per-PR / preview config). The branch inherits the parent's secrets by layering at read time — nothing is copied or re-encrypted — and holds only the values you override on it. Generates the branch's data key locally, grants it to the caller, and shares it with the parent's other readers.", {
537
+ org: z.string().optional(),
538
+ app: z.string(),
539
+ from: z.string().describe("the environment to branch"),
540
+ slug: z.string().describe("the branch name, e.g. pr-142"),
541
+ ttl: z.string().optional().describe("lifetime: 12h, 7d, 2w, … or `never` (default 7d)")
542
+ }, async ({ org, app, from, slug, ttl }) => {
543
+ const ctx = getCtx();
544
+ const parent = await resolveAppEnv(ctx, {
545
+ org,
546
+ app,
547
+ env: from
548
+ });
549
+ const parsedTtl = parseBranchTtl(ttl ?? "7d");
550
+ if (parsedTtl === null) throw new Error(`invalid ttl "${ttl}" (try 12h, 7d, 2w, or never)`);
551
+ const me = await kmsCallerIdentity(ctx);
552
+ const dek = generateDek();
553
+ const wrappedDek = await wrapDek(dek, me.publicKeyJwk);
554
+ const grants = [];
555
+ const { grantees } = await ctx.client.listGrantees(parent.orgId, parent.envId);
556
+ for (const grantee of grantees) {
557
+ if (grantee.principalType === me.principalType && grantee.principalId === me.principalId) continue;
558
+ grants.push({
559
+ principalType: grantee.principalType,
560
+ principalId: grantee.principalId,
561
+ wrappedDek: await wrapDek(dek, grantee.publicKeyJwk)
562
+ });
563
+ }
564
+ return (await ctx.client.createBranch(parent.orgId, parent.envId, {
565
+ slug,
566
+ ttlSeconds: Number.isFinite(parsedTtl) ? parsedTtl : null,
567
+ wrappedDek,
568
+ grants
569
+ })).branch;
570
+ });
571
+ tool("delete_branch", "Tear down a branch config and every value it overrode. The parent environment is untouched.", {
572
+ org: z.string().optional(),
573
+ app: z.string(),
574
+ branch: z.string()
575
+ }, async ({ org, app, branch }) => {
576
+ const ctx = getCtx();
577
+ const appRef = await resolveApp(ctx, {
578
+ org,
579
+ app
580
+ });
581
+ const target = await resolveBranch(ctx, appRef, branch);
582
+ await ctx.client.deleteBranch(appRef.orgId, target.id);
583
+ return { deleted: target.slug };
584
+ });
517
585
  tool("create_group_env", "Create a group environment. Generates the data key locally and grants it to the caller.", {
518
586
  org: z.string().optional(),
519
587
  group: z.string(),
@@ -573,7 +641,7 @@ async function runMcpServer(options = {}) {
573
641
  await ctx.client.unlinkEnvGroup(target.orgId, target.envId, g.id);
574
642
  return { ok: true };
575
643
  });
576
- tool("set_secret", "Encrypt a value locally and store it in an environment.", {
644
+ tool("set_secret", "Encrypt a value locally and store it in an environment. A value may reference another secret as ${OTHER_SECRET}: the reference is stored literally and expanded whenever the secret is read, so it tracks the referenced value. Write $${OTHER_SECRET} for a literal.", {
577
645
  ...targetShape,
578
646
  name: z.string(),
579
647
  value: z.string()
@@ -587,10 +655,11 @@ async function runMcpServer(options = {}) {
587
655
  name: o.name
588
656
  };
589
657
  });
590
- tool("get_secret", "Return one secret. By default only reports presence + version; pass reveal:true to decrypt the plaintext into this response (avoid unless the value is actually needed — prefer run_command). Pass `version` to read an earlier version instead of the current one.", {
658
+ tool("get_secret", "Return one secret. By default only reports presence + version; pass reveal:true to decrypt the plaintext into this response (avoid unless the value is actually needed — prefer run_command). A revealed current value has its ${OTHER_SECRET} references expanded against this environment's own secrets; pass raw:true for the stored text instead. Pass `version` to read an earlier version instead of the current one (always as stored, never expanded).", {
591
659
  ...targetShape,
592
660
  name: z.string(),
593
661
  reveal: z.boolean().optional(),
662
+ raw: z.boolean().optional().describe("skip ${OTHER_SECRET} expansion (with reveal)"),
594
663
  version: z.number().int().positive().optional().describe("an earlier version from list_secret_versions (default: current)")
595
664
  }, async (o) => {
596
665
  const ctx = getCtx();
@@ -615,7 +684,7 @@ async function runMcpServer(options = {}) {
615
684
  revealed: true
616
685
  };
617
686
  }
618
- const values = await fetchDecryptedSecrets(ctx, orgId, envId);
687
+ const values = await fetchDecryptedSecrets(ctx, orgId, envId, { raw: o.raw });
619
688
  if (!(o.name in values)) throw new Error(`no secret named ${o.name}`);
620
689
  return {
621
690
  name: o.name,
@@ -674,6 +743,7 @@ async function runMcpServer(options = {}) {
674
743
  org: z.string().optional(),
675
744
  app: z.string().optional(),
676
745
  env: z.string().optional().describe("environment slug (token auth infers this)"),
746
+ branch: z.string().optional().describe("read an ephemeral branch of that environment"),
677
747
  with: z.record(z.string(), z.string()).optional().describe("group=env slice overrides"),
678
748
  envFile: z.array(z.string()).optional().describe(".env files to overlay (default [.env])"),
679
749
  cwd: z.string().optional()
@@ -695,6 +765,7 @@ async function runMcpServer(options = {}) {
695
765
  org: z.string().optional(),
696
766
  app: z.string().optional(),
697
767
  env: z.string().optional(),
768
+ branch: z.string().optional().describe("read an ephemeral branch of that environment"),
698
769
  with: z.record(z.string(), z.string()).optional()
699
770
  }, async (o) => {
700
771
  const ctx = getCtx();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekrit/cli",
3
- "version": "0.24.0",
3
+ "version": "0.26.0",
4
4
  "description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -26,8 +26,8 @@
26
26
  "devDependencies": {
27
27
  "@types/node": "^26.1.0",
28
28
  "tsdown": "^0.22.3",
29
- "@seekrit/api-client": "0.0.1",
30
29
  "@seekrit/core": "0.0.1",
30
+ "@seekrit/api-client": "0.0.1",
31
31
  "@seekrit/crypto": "0.0.1"
32
32
  },
33
33
  "scripts": {