@seekrit/cli 0.19.0 → 0.20.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/index.js CHANGED
@@ -1976,7 +1976,7 @@ function isServiceToken(value) {
1976
1976
  }
1977
1977
  //#endregion
1978
1978
  //#region package.json
1979
- var version = "0.19.0";
1979
+ var version = "0.20.0";
1980
1980
  //#endregion
1981
1981
  //#region ../../packages/api-client/src/index.ts
1982
1982
  var SeekritApiError = class extends Error {
@@ -2005,7 +2005,8 @@ var SeekritClient = class {
2005
2005
  const token = await this.auth.getToken();
2006
2006
  if (!token) throw new SeekritApiError(401, "unauthorized", "session expired");
2007
2007
  headers.authorization = `Bearer ${token}`;
2008
- } else headers["x-seekrit-dev-user"] = this.auth.email;
2008
+ } else if (this.auth.type === "m2m") headers.authorization = `Basic ${btoa(`${this.auth.clientId}:${this.auth.clientSecret}`)}`;
2009
+ else headers["x-seekrit-dev-user"] = this.auth.email;
2009
2010
  if (body !== void 0) headers["content-type"] = "application/json";
2010
2011
  const res = await this.fetchImpl(`${this.baseUrl}${path}`, {
2011
2012
  method,
@@ -2355,6 +2356,17 @@ function writeGlobalConfig(update) {
2355
2356
  mkdirSync(dirname(path), { recursive: true });
2356
2357
  writeFileSync(path, `${JSON.stringify(merged, null, 2)}\n`, { mode: 384 });
2357
2358
  }
2359
+ /** The admin token previously auto-minted from these M2M credentials, if any. */
2360
+ function readCachedM2mToken(clientId) {
2361
+ return readGlobalConfig().m2mTokens?.[clientId];
2362
+ }
2363
+ /** Remember an auto-minted admin token so the next run reuses it. */
2364
+ function cacheM2mToken(clientId, token) {
2365
+ writeGlobalConfig({ m2mTokens: {
2366
+ ...readGlobalConfig().m2mTokens,
2367
+ [clientId]: token
2368
+ } });
2369
+ }
2358
2370
  /** Walk up from cwd looking for seekrit.json. */
2359
2371
  function findProjectConfig(startDir = process.cwd()) {
2360
2372
  let dir = startDir;
@@ -2451,7 +2463,7 @@ function tryBuildContext(dotenvVars = {}) {
2451
2463
  }
2452
2464
  function buildContext() {
2453
2465
  const ctx = tryBuildContext();
2454
- if (!ctx) fail("no credentials found — run `seekrit login --token skt_…`, or set SEEKRIT_TOKEN / SEEKRIT_DEV_USER");
2466
+ if (!ctx) fail("no credentials found — run `seekrit login --token skt_…` (or `--client-id … --client-secret …`), or set SEEKRIT_TOKEN / SEEKRIT_CLIENT_ID + SEEKRIT_CLIENT_SECRET / SEEKRIT_DEV_USER");
2455
2467
  return ctx;
2456
2468
  }
2457
2469
  function isTokenAuth(ctx) {
@@ -3177,6 +3189,77 @@ function registerKmsCommands(program) {
3177
3189
  });
3178
3190
  }
3179
3191
  //#endregion
3192
+ //#region src/m2m.ts
3193
+ /**
3194
+ * Resolve M2M client credentials from (in order) the process environment, a
3195
+ * `.env` overlay (for `seekrit run`), then saved config. Both halves must come
3196
+ * through for the credential to be usable.
3197
+ */
3198
+ function readM2mCreds(dotenvVars = {}) {
3199
+ const config = readGlobalConfig();
3200
+ const fromEnv = (key) => process.env[key] ?? dotenvVars[key];
3201
+ const clientId = fromEnv("SEEKRIT_CLIENT_ID") ?? config.clientId;
3202
+ const clientSecret = fromEnv("SEEKRIT_CLIENT_SECRET") ?? config.clientSecret;
3203
+ if (!clientId || !clientSecret) return null;
3204
+ return {
3205
+ clientId,
3206
+ clientSecret
3207
+ };
3208
+ }
3209
+ /** True when a service/dev credential is already configured explicitly. */
3210
+ function hasExplicitCredential(dotenvVars) {
3211
+ const fromEnv = (key) => process.env[key] ?? dotenvVars[key];
3212
+ if (fromEnv("SEEKRIT_TOKEN") || fromEnv("SEEKRIT_DEV_USER")) return true;
3213
+ const config = readGlobalConfig();
3214
+ return Boolean(config.token || config.devUser);
3215
+ }
3216
+ /** Mint a fresh org-scoped admin token using M2M credentials (keyless). */
3217
+ async function mintAdminToken(apiUrl, creds) {
3218
+ const client = new SeekritClient({
3219
+ baseUrl: apiUrl,
3220
+ auth: {
3221
+ type: "m2m",
3222
+ clientId: creds.clientId,
3223
+ clientSecret: creds.clientSecret
3224
+ }
3225
+ });
3226
+ const { orgs } = await client.listOrgs();
3227
+ const org = orgs[0];
3228
+ if (!org) throw new Error("machine client has no organization — sign up first (POST /signup)");
3229
+ const created = await createServiceToken();
3230
+ await client.createToken(org.id, {
3231
+ name: "agent-admin (auto)",
3232
+ tokenId: created.tokenId,
3233
+ tokenHash: created.tokenHash,
3234
+ publicKeyJwk: created.publicKeyJwk,
3235
+ role: "admin",
3236
+ environmentId: null
3237
+ });
3238
+ return created.token;
3239
+ }
3240
+ /**
3241
+ * If M2M credentials are configured (and no explicit token/dev-user is), ensure
3242
+ * an admin token exists for them — minting and caching one on first use — and
3243
+ * expose it as `SEEKRIT_TOKEN` so the normal credential resolution
3244
+ * ({@link tryBuildContext}) picks it up. A no-op (returns null) when there is
3245
+ * nothing to bootstrap. Throws only when credentials ARE present but minting
3246
+ * fails, so the reason surfaces instead of a later opaque "no credentials".
3247
+ */
3248
+ async function ensureM2mAdminToken(dotenvVars = {}) {
3249
+ if (hasExplicitCredential(dotenvVars)) return null;
3250
+ const creds = readM2mCreds(dotenvVars);
3251
+ if (!creds) return null;
3252
+ const cached = readCachedM2mToken(creds.clientId);
3253
+ if (cached) {
3254
+ process.env.SEEKRIT_TOKEN = cached;
3255
+ return cached;
3256
+ }
3257
+ const token = await mintAdminToken(process.env.SEEKRIT_API_URL ?? dotenvVars.SEEKRIT_API_URL ?? readGlobalConfig().apiUrl ?? "https://api.seekrit.dev", creds);
3258
+ cacheM2mToken(creds.clientId, token);
3259
+ process.env.SEEKRIT_TOKEN = token;
3260
+ return token;
3261
+ }
3262
+ //#endregion
3180
3263
  //#region src/mongodb.ts
3181
3264
  /**
3182
3265
  * `seekrit mongodb` — temporary MongoDB credentials (Vault-style dynamic
@@ -4225,10 +4308,16 @@ function collectList(value, acc = []) {
4225
4308
  return acc;
4226
4309
  }
4227
4310
  const program = new Command("seekrit").description("End-to-end encrypted secrets manager").version(version).enablePositionalOptions();
4228
- program.command("login").description("store credentials for the API").option("--token <token>", "service token (skt_…)").option("--dev-user <email>", "dev-mode identity (local API with AUTH_MODE=dev)").option("--api-url <url>", "API base URL").action((options) => {
4311
+ program.hook("preAction", async () => {
4312
+ await ensureM2mAdminToken();
4313
+ });
4314
+ program.command("login").description("store credentials for the API").option("--token <token>", "service token (skt_…)").option("--client-id <id>", "machine (M2M) client id — auto-mints an admin token").option("--client-secret <secret>", "machine (M2M) client secret").option("--dev-user <email>", "dev-mode identity (local API with AUTH_MODE=dev)").option("--api-url <url>", "API base URL").action((options) => {
4229
4315
  if (options.token && !isServiceToken(options.token)) fail("token must start with skt_");
4316
+ if (Boolean(options.clientId) !== Boolean(options.clientSecret)) fail("--client-id and --client-secret must be given together");
4230
4317
  writeGlobalConfig({
4231
4318
  ...options.token ? { token: options.token } : {},
4319
+ ...options.clientId ? { clientId: options.clientId } : {},
4320
+ ...options.clientSecret ? { clientSecret: options.clientSecret } : {},
4232
4321
  ...options.devUser ? { devUser: options.devUser } : {},
4233
4322
  ...options.apiUrl ? { apiUrl: options.apiUrl } : {}
4234
4323
  });
@@ -4451,6 +4540,7 @@ async function materializeForRun(options) {
4451
4540
  const dotenvVars = {};
4452
4541
  overlayEnvFiles(dotenvVars, {}, envFiles);
4453
4542
  try {
4543
+ await ensureM2mAdminToken(dotenvVars);
4454
4544
  const ctx = tryBuildContext(dotenvVars);
4455
4545
  if (!ctx) throw new Error("no credentials found (set SEEKRIT_TOKEN / SEEKRIT_DEV_USER or run `seekrit login`)");
4456
4546
  return await materialize(ctx, options);
@@ -4615,7 +4705,7 @@ registerMongoCommands(program);
4615
4705
  registerKmsCommands(program);
4616
4706
  registerRecoveryCommands(program);
4617
4707
  program.command("mcp").description("run an MCP server over stdio so AI agents can drive seekrit").action(async () => {
4618
- const { runMcpServer } = await import("./mcp-DR_Ghb4w.js");
4708
+ const { runMcpServer } = await import("./mcp-DScHFOMG.js");
4619
4709
  await runMcpServer();
4620
4710
  });
4621
4711
  program.command("audit").description("show the org audit trail").option("--org <slug>").option("--limit <n>", "entries to fetch", "50").action(async (options) => {
@@ -4629,4 +4719,4 @@ program.parseAsync(argv).catch((err) => {
4629
4719
  fail(err instanceof Error ? err.message : String(err));
4630
4720
  });
4631
4721
  //#endregion
4632
- export { generateEncryptKeyMaterial as A, importVerifyingKey as C, generatePostgresCredential as D, verifyMessage as E, generateDek as F, toBase64 as I, kmsDecrypt as M, kmsEncrypt as N, generateMysqlCredential as O, wrapDek as P, importSigningKey as S, signatureKeyRef as T, version as _, kmsRecoverMaterial as a, parseServiceToken as b, resolveAppEnv as c, resolveOrg as d, getDek as f, writeProjectConfig as g, setFailThrows as h, kmsCallerIdentity as i, kmsBlobKeyRef as j, generateDataKey as k, resolveEnvTarget as l, tryBuildContext as m, fetchDecryptedSecrets as n, kmsResolveKey as o, isTokenAuth as p, materializeEnv as r, kmsResolveRecipient as s, encryptAndSetSecret as t, resolveGroup as u, createServiceToken as v, signMessage as w, generateSigningKeyMaterial as x, isServiceToken as y };
4722
+ export { generateDataKey as A, importSigningKey as C, verifyMessage as D, signatureKeyRef as E, wrapDek as F, generateDek as I, toBase64 as L, kmsBlobKeyRef as M, kmsDecrypt as N, generatePostgresCredential as O, kmsEncrypt as P, generateSigningKeyMaterial as S, signMessage as T, writeProjectConfig as _, kmsCallerIdentity as a, isServiceToken as b, kmsResolveRecipient as c, resolveGroup as d, resolveOrg as f, setFailThrows as g, tryBuildContext as h, ensureM2mAdminToken as i, generateEncryptKeyMaterial as j, generateMysqlCredential as k, resolveAppEnv as l, isTokenAuth as m, fetchDecryptedSecrets as n, kmsRecoverMaterial as o, getDek as p, materializeEnv as r, kmsResolveKey as s, encryptAndSetSecret as t, resolveEnvTarget as u, version as v, importVerifyingKey as w, parseServiceToken as x, createServiceToken as y };
@@ -1,4 +1,4 @@
1
- import { A as generateEncryptKeyMaterial, C as importVerifyingKey, D as generatePostgresCredential, E as verifyMessage, F as generateDek, I as toBase64, M as kmsDecrypt, N as kmsEncrypt, O as generateMysqlCredential, P as wrapDek, S as importSigningKey, T as signatureKeyRef, _ as version, a as kmsRecoverMaterial, b as parseServiceToken, c as resolveAppEnv, d as resolveOrg, f as getDek, g as writeProjectConfig, h as setFailThrows, i as kmsCallerIdentity, j as kmsBlobKeyRef, k as generateDataKey, l as resolveEnvTarget, m as tryBuildContext, n as fetchDecryptedSecrets, o as kmsResolveKey, p as isTokenAuth, r as materializeEnv, s as kmsResolveRecipient, t as encryptAndSetSecret, u as resolveGroup, v as createServiceToken, w as signMessage, x as generateSigningKeyMaterial, y as isServiceToken } from "./index.js";
1
+ import { A as generateDataKey, C as importSigningKey, D as verifyMessage, E as signatureKeyRef, F as wrapDek, I as generateDek, L as toBase64, M as kmsBlobKeyRef, N as kmsDecrypt, O as generatePostgresCredential, P as kmsEncrypt, S as generateSigningKeyMaterial, T as signMessage, _ as writeProjectConfig, a as kmsCallerIdentity, b as isServiceToken, c as kmsResolveRecipient, d as resolveGroup, f as resolveOrg, g as setFailThrows, h as tryBuildContext, i as ensureM2mAdminToken, j as generateEncryptKeyMaterial, k as generateMysqlCredential, l as resolveAppEnv, m as isTokenAuth, n as fetchDecryptedSecrets, o as kmsRecoverMaterial, p as getDek, r as materializeEnv, s as kmsResolveKey, t as encryptAndSetSecret, u as resolveEnvTarget, v as version, w as importVerifyingKey, x as parseServiceToken, y as createServiceToken } 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";
@@ -29,7 +29,7 @@ function errText(err) {
29
29
  /** Build the client context or throw a friendly, agent-readable error. */
30
30
  function getCtx() {
31
31
  const ctx = tryBuildContext();
32
- if (!ctx) throw new Error("no credentials — set SEEKRIT_TOKEN (a skt_… token) or SEEKRIT_DEV_USER in the MCP server env, or run `seekrit login` first");
32
+ if (!ctx) throw new Error("no credentials — set SEEKRIT_CLIENT_ID + SEEKRIT_CLIENT_SECRET (machine credentials from POST /signup) or SEEKRIT_TOKEN (a skt_… token) in the MCP server env, or run `seekrit login` first");
33
33
  return ctx;
34
34
  }
35
35
  /**
@@ -117,6 +117,7 @@ async function resolveTargetEnv(ctx, o) {
117
117
  }
118
118
  async function runMcpServer(options = {}) {
119
119
  setFailThrows(true);
120
+ await ensureM2mAdminToken();
120
121
  const server = new McpServer({
121
122
  name: "seekrit",
122
123
  version: options.version ?? version
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekrit/cli",
3
- "version": "0.19.0",
3
+ "version": "0.20.0",
4
4
  "description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -27,8 +27,8 @@
27
27
  "@types/node": "^26.1.0",
28
28
  "tsdown": "^0.22.3",
29
29
  "@seekrit/api-client": "0.0.1",
30
- "@seekrit/crypto": "0.0.1",
31
- "@seekrit/core": "0.0.1"
30
+ "@seekrit/core": "0.0.1",
31
+ "@seekrit/crypto": "0.0.1"
32
32
  },
33
33
  "scripts": {
34
34
  "build": "tsdown",