@seekrit/cli 1.1.0 → 1.2.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.
package/README.md CHANGED
@@ -30,11 +30,12 @@ seekrit login --token skt_…
30
30
  # leaves this machine). Only needed once per account.
31
31
  seekrit keys setup
32
32
 
33
- # 3. Link the current directory to an org / app / environment. Writes
34
- # seekrit.json, which is safe to commit.
35
- seekrit init --org acme --app storefront --env production
33
+ # 3. Link the current directory to an org / app. Writes seekrit.json, which is
34
+ # safe to commit. The environment comes from the token at runtime.
35
+ seekrit init --org acme --app storefront
36
36
 
37
- # 4. Work with secrets
37
+ # 4. Work with secrets. A service token names its own environment; on a user
38
+ # session, add --env production.
38
39
  seekrit secrets set DATABASE_URL 'postgres://…'
39
40
  seekrit secrets list
40
41
  seekrit run -- ./server # runs ./server with secrets in its env
@@ -55,7 +56,7 @@ Run `seekrit <command> --help` for full flags. The
55
56
  | `login [--token \| --dev-user] [--api-url]` | Save credentials to the global config. |
56
57
  | `whoami` | Show the authenticated identity and org membership. |
57
58
  | `keys setup` | Generate your keypair and protect it with a passphrase. |
58
- | `init --org --app --env` | Link this directory to an environment (`seekrit.json`). |
59
+ | `init --org --app` | Link this directory to an org/app (`seekrit.json`). The environment comes from the token. |
59
60
  | `org create` / `app create` / `env create` | Create organizations, apps, and environments. |
60
61
  | `secrets list` | List secret names (never values). |
61
62
  | `secrets get <name> [--raw]` | Decrypt and print one secret value (`--raw` skips `${OTHER_SECRET}` expansion). |
package/dist/index.js CHANGED
@@ -1907,6 +1907,8 @@ const AUDIT_ACTIONS = [
1907
1907
  "org.mfa_policy_changed",
1908
1908
  "org.exported",
1909
1909
  "user.keys_updated",
1910
+ "user.passkey_enrolled",
1911
+ "user.passkey_removed",
1910
1912
  "user.notification_prefs_updated",
1911
1913
  "app.created",
1912
1914
  "app.updated",
@@ -2151,6 +2153,16 @@ z.object({
2151
2153
  */
2152
2154
  encryptedPrivateKey: z.string().min(1)
2153
2155
  });
2156
+ z.object({
2157
+ /** Base64url WebAuthn credential id — what `allowCredentials` is built from. */
2158
+ credentialId: z.string().min(1).max(512),
2159
+ /** Human label for the device, e.g. "MacBook Touch ID". Display only. */
2160
+ label: z.string().min(1).max(64),
2161
+ /** Base64url PRF evaluation input for this credential; not a secret. */
2162
+ prfInput: z.string().min(1).max(256),
2163
+ /** The private key wrapped to this passkey's PRF output. */
2164
+ encryptedPrivateKey: z.string().min(1).max(8192).refine((v) => v.startsWith("pk2."), { message: "expected a pk2. passkey wrap" })
2165
+ });
2154
2166
  const grantEnvironmentKeySchema = z.object({
2155
2167
  principalType: principalTypeSchema,
2156
2168
  principalId: z.string().min(1),
@@ -2483,7 +2495,8 @@ const SYNC_PROVIDER_KINDS = [
2483
2495
  "github-actions",
2484
2496
  "gcp-secret-manager",
2485
2497
  "langgraph-platform",
2486
- "azure-key-vault"
2498
+ "azure-key-vault",
2499
+ "huggingface-spaces"
2487
2500
  ];
2488
2501
  z.enum(SYNC_PROVIDER_KINDS);
2489
2502
  /**
@@ -2872,6 +2885,21 @@ const azureKeyVaultConnectionConfigSchema = z.object({
2872
2885
  /** Which Azure cloud the tenant and its vaults live in. */
2873
2886
  cloud: z.enum(AZURE_CLOUDS).default("public")
2874
2887
  });
2888
+ /**
2889
+ * Hugging Face account scope — empty, as Render's and Fly's are.
2890
+ *
2891
+ * Neither half of "which account, which thing" needs stating. A Hub user access
2892
+ * token belongs to one user and carries their write access to every Space they
2893
+ * or their organizations own; a Space is addressed by `owner/name`, which is
2894
+ * globally unique. So the token plus the destination is the whole address.
2895
+ *
2896
+ * There is deliberately no `baseUrl` twin of the GitHub Enterprise Server
2897
+ * field. `HF_ENDPOINT` exists in the Python client for Hub *mirrors*, which
2898
+ * serve repository content — not the settings API this connector writes, and
2899
+ * not something a mirror is expected to accept a write on. Adding the field
2900
+ * would invite pointing a connection at a host that silently swallows secrets.
2901
+ */
2902
+ const huggingfaceSpacesConnectionConfigSchema = z.object({ provider: z.literal("huggingface-spaces") });
2875
2903
  const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
2876
2904
  vercelConnectionConfigSchema,
2877
2905
  cloudflareWorkersConnectionConfigSchema,
@@ -2890,7 +2918,8 @@ const syncConnectionConfigSchema = z.discriminatedUnion("provider", [
2890
2918
  githubActionsConnectionConfigSchema,
2891
2919
  gcpSecretManagerConnectionConfigSchema,
2892
2920
  langgraphPlatformConnectionConfigSchema,
2893
- azureKeyVaultConnectionConfigSchema
2921
+ azureKeyVaultConnectionConfigSchema,
2922
+ huggingfaceSpacesConnectionConfigSchema
2894
2923
  ]);
2895
2924
  /** Vercel's three deployment targets. A binding writes to one or more. */
2896
2925
  const VERCEL_TARGETS = [
@@ -3681,6 +3710,40 @@ const azureKeyVaultDestinationSchema = z.object({
3681
3710
  /** What to do with a name Key Vault cannot store — see {@link AZURE_KEY_VAULT_NAME_MODES}. */
3682
3711
  nameMode: z.enum(AZURE_KEY_VAULT_NAME_MODES).default("dash")
3683
3712
  });
3713
+ /**
3714
+ * The Space whose secrets a binding owns, addressed the way the Hub addresses
3715
+ * every repository: `owner/name`, where `owner` is a user or an organization.
3716
+ *
3717
+ * A Space has **one** secret set, shared by every replica — there is no
3718
+ * per-target split to state, the way Vercel and Pages have one. The Hub's
3719
+ * convention is that staging and production are separate Spaces
3720
+ * (`acme/demo`, `acme/demo-staging`), so pointing at an environment means
3721
+ * naming that Space, exactly as a Fly environment means naming its own app.
3722
+ *
3723
+ * Validated by shape because the two habitual slips both fail *late*: pasting
3724
+ * the browser URL (`https://huggingface.co/spaces/acme/demo`) or the bare name
3725
+ * without its owner. Either one is a 404 from the Hub inside an alarm with
3726
+ * nobody watching, and a 404 says nothing about which half was wrong. The
3727
+ * leading character is held to alphanumeric, which is also what rejects the
3728
+ * `.` and `..` that no repository may be called.
3729
+ */
3730
+ const huggingfaceRepoIdSchema = z.string().trim().regex(/^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/, "must be a Space ID as owner/name, e.g. acme/support-demo — not a URL");
3731
+ /**
3732
+ * One Hugging Face **Space**, whose secrets arrive in the app's container as
3733
+ * environment variables.
3734
+ *
3735
+ * Only secrets. A Space also has *variables*, and they are not a second lane
3736
+ * seekrit could use: the Hub calls them "non-sensitive configuration values",
3737
+ * they are "publicly accessible and viewable", and they are copied into every
3738
+ * Space duplicated from this one. Writing a seekrit secret there would publish
3739
+ * it, so this connector has no variables mode — a binding that wants one is
3740
+ * asking for the wrong thing.
3741
+ */
3742
+ const huggingfaceSpacesDestinationSchema = z.object({
3743
+ provider: z.literal("huggingface-spaces"),
3744
+ /** Space ID as `owner/name`, e.g. `acme/support-demo`. */
3745
+ repoId: huggingfaceRepoIdSchema
3746
+ });
3684
3747
  const syncDestinationSchema = z.discriminatedUnion("provider", [
3685
3748
  vercelDestinationSchema,
3686
3749
  cloudflareWorkersDestinationSchema,
@@ -3699,7 +3762,8 @@ const syncDestinationSchema = z.discriminatedUnion("provider", [
3699
3762
  githubActionsDestinationSchema,
3700
3763
  gcpSecretManagerDestinationSchema,
3701
3764
  langgraphPlatformDestinationSchema,
3702
- azureKeyVaultDestinationSchema
3765
+ azureKeyVaultDestinationSchema,
3766
+ huggingfaceSpacesDestinationSchema
3703
3767
  ]);
3704
3768
  /**
3705
3769
  * How seekrit secret names become destination key names. Applied in order:
@@ -4987,7 +5051,7 @@ async function createAgentTaskToken() {
4987
5051
  }
4988
5052
  //#endregion
4989
5053
  //#region package.json
4990
- var version = "1.1.0";
5054
+ var version = "1.2.1";
4991
5055
  //#endregion
4992
5056
  //#region ../../packages/api-client/src/index.ts
4993
5057
  var SeekritApiError = class extends Error {
@@ -5060,6 +5124,22 @@ var SeekritClient = class {
5060
5124
  revokeCliSession(sessionId) {
5061
5125
  return this.request("DELETE", `/v1/me/cli-sessions/${sessionId}`);
5062
5126
  }
5127
+ /**
5128
+ * The passkeys enrolled to unlock this user's keyring, each with the `pk2.`
5129
+ * blob its PRF output decrypts. One call is everything the unlock ceremony
5130
+ * needs; the blobs are opaque without the authenticator.
5131
+ */
5132
+ listMyPasskeys() {
5133
+ return this.request("GET", "/v1/me/passkeys");
5134
+ }
5135
+ /** File a private key already wrapped, client-side, to a passkey's PRF output. */
5136
+ enrollMyPasskey(input) {
5137
+ return this.request("POST", "/v1/me/passkeys", input);
5138
+ }
5139
+ /** Stop a passkey unlocking the keyring. The key itself is untouched. */
5140
+ deleteMyPasskey(passkeyId) {
5141
+ return this.request("DELETE", `/v1/me/passkeys/${passkeyId}`);
5142
+ }
5063
5143
  /** What a pending login request is asking for — for the approval screen. */
5064
5144
  getCliLoginRequest(code) {
5065
5145
  return this.request("GET", `/v1/cli-login/${encodeURIComponent(code)}`);
@@ -6066,11 +6146,46 @@ async function resolveOrg(ctx, orgSlug) {
6066
6146
  fail("specify --org (or run `seekrit init`)");
6067
6147
  }
6068
6148
  /**
6149
+ * The environment a service token is bound to.
6150
+ *
6151
+ * The binding lives on the token row, not in the token string — `GET
6152
+ * /v1/resolve` is where the API publishes it, and `seekrit whoami` reads it the
6153
+ * same way. One round trip, and it also resolves `--branch` against the bound
6154
+ * environment, which is the only environment a token may name a branch of.
6155
+ */
6156
+ async function boundEnvTarget(ctx, opts) {
6157
+ let scope;
6158
+ try {
6159
+ ({scope} = await ctx.client.resolve(opts.branch ? { branch: opts.branch } : {}));
6160
+ } catch (err) {
6161
+ fail(`specify --env — this token has no environment of its own to fall back to (${err instanceof Error ? err.message : String(err)})`);
6162
+ }
6163
+ const env = scope.branchOf ?? {
6164
+ envId: scope.envId,
6165
+ envSlug: scope.envSlug
6166
+ };
6167
+ const label = scope.branchOf ? `${scope.appSlug}/${scope.branchOf.envSlug}#${scope.envSlug}` : `${scope.appSlug}/${scope.envSlug}`;
6168
+ if (opts.org && opts.org !== scope.orgSlug && opts.org !== scope.orgId) fail(`this token belongs to ${scope.orgSlug}, not "${opts.org}"`);
6169
+ if (opts.env && opts.env !== env.envSlug && opts.env !== env.envId) fail(`this token is bound to ${scope.appSlug}/${env.envSlug}, not "${opts.env}" — add --app (or --group) to target another environment you hold a key for`);
6170
+ return {
6171
+ orgId: scope.orgId,
6172
+ envId: scope.envId,
6173
+ label
6174
+ };
6175
+ }
6176
+ /**
6069
6177
  * Resolve an environment to operate on — an application env (`--app --env`,
6070
6178
  * or the config's app + `--env`), a branch of one (`--branch`), or a group env
6071
6179
  * (`--group --env`).
6180
+ *
6181
+ * `--env` is optional for a service token that names no `--app`/`--group`: it is
6182
+ * already bound to exactly one environment, so requiring the flag made every
6183
+ * documented one-liner (`seekrit secrets list`) fail for the CI job and the
6184
+ * agent that are the point of a token. The local MCP server has always inferred
6185
+ * it this way; this is the same rule, in one place, for both.
6072
6186
  */
6073
6187
  async function resolveEnvTarget(ctx, opts) {
6188
+ if (!opts.app && !opts.group && isTokenAuth(ctx)) return boundEnvTarget(ctx, opts);
6074
6189
  const org = await resolveOrg(ctx, opts.org);
6075
6190
  if (!opts.env) fail("specify --env");
6076
6191
  if (opts.group) {
@@ -12949,6 +13064,18 @@ function assertLanggraphDeploymentId(value) {
12949
13064
  if (!/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(id)) fail(`--langgraph-deployment should be the deployment UUID from its dashboard URL, not "${id}"`);
12950
13065
  return id;
12951
13066
  }
13067
+ /**
13068
+ * A Hugging Face Space is addressed as `owner/name`. The two habitual slips are
13069
+ * pasting the browser URL and giving the bare name without its owner; the first
13070
+ * is recovered rather than refused, since a Space URL has exactly one shape.
13071
+ */
13072
+ function assertHuggingfaceSpace(value) {
13073
+ if (!value) fail("--hf-space is required for huggingface-spaces (owner/name)");
13074
+ const raw = value.trim();
13075
+ const id = raw.match(/^https?:\/\/(?:[^/]*\.)?huggingface\.co\/spaces\/([^/?#]+\/[^/?#]+)(?:[/?#]|$)/i)?.[1] ?? raw.replace(/^\/+|\/+$/g, "");
13076
+ if (!/^[A-Za-z0-9][A-Za-z0-9._-]{0,95}\/[A-Za-z0-9][A-Za-z0-9._-]{0,95}$/.test(id)) fail(`--hf-space should be the Space ID as owner/name, e.g. acme/support-demo, not "${raw}"`);
13077
+ return id;
13078
+ }
12952
13079
  /** Reject a single value outside a known set, naming the choices. */
12953
13080
  function assertMember(value, allowed, flag, fallback) {
12954
13081
  if (value === void 0) return fallback;
@@ -13075,6 +13202,7 @@ function credentialNoun(provider) {
13075
13202
  if (provider === "gcp-secret-manager") return "service-account key JSON";
13076
13203
  if (provider === "langgraph-platform") return "LangSmith API key";
13077
13204
  if (provider === "azure-key-vault") return "client secret";
13205
+ if (provider === "huggingface-spaces") return "user access token";
13078
13206
  return "API token";
13079
13207
  }
13080
13208
  /**
@@ -13163,6 +13291,7 @@ function buildConfig(provider, options) {
13163
13291
  cloud: cloud ?? "public"
13164
13292
  };
13165
13293
  }
13294
+ case "huggingface-spaces": return { provider: "huggingface-spaces" };
13166
13295
  }
13167
13296
  }
13168
13297
  /** Where inside the platform a binding writes. */
@@ -13376,6 +13505,10 @@ function buildDestination(provider, options) {
13376
13505
  nameMode: nameMode ?? "dash"
13377
13506
  };
13378
13507
  }
13508
+ case "huggingface-spaces": return {
13509
+ provider: "huggingface-spaces",
13510
+ repoId: assertHuggingfaceSpace(options.hfSpace)
13511
+ };
13379
13512
  }
13380
13513
  }
13381
13514
  /** One-line description of a destination, for list output. */
@@ -13398,6 +13531,7 @@ function describeDestination(destination) {
13398
13531
  case "gcp-secret-manager": return destination.layout === "json-bundle" ? `${destination.secretId} · json` : `${destination.idPrefix ?? ""}* · one per name`;
13399
13532
  case "langgraph-platform": return `deployment ${destination.deploymentId}`;
13400
13533
  case "azure-key-vault": return `${destination.vault}${destination.prefix ? ` (${destination.prefix}*)` : ""}`;
13534
+ case "huggingface-spaces": return `space ${destination.repoId}`;
13401
13535
  case "github-actions": switch (destination.kind) {
13402
13536
  case "repo": return `${destination.owner}/${destination.repo}`;
13403
13537
  case "environment": return `${destination.owner}/${destination.repo} @${destination.environment}`;
@@ -13413,7 +13547,7 @@ function describeDestination(destination) {
13413
13547
  * application whose environment the binding reads from.
13414
13548
  */
13415
13549
  function destinationOptions(command) {
13416
- return command.option("--project <id>", "vercel: project id or name · cloudflare-pages / northflank: project name or slug · bunnyshell: project ID (writes variables new environments inherit)").option("--target <list>", "vercel / cloudflare-pages: comma-separated deployment targets", "production").option("--git-branch <branch>", "vercel: restrict preview writes to one branch").option("--script <name>", "cloudflare-workers: Worker script name").option("--store-id <id>", "cloudflare-secrets-store: store ID (32 hex)").option("--scopes <list>", "cloudflare-secrets-store: comma-separated scopes", "workers").option("--railway-project <id>", "railway: project ID (a UUID)").option("--railway-environment <id>", "railway: environment ID (a UUID)").option("--service <id>", "railway: service ID (omit for the environment's shared variables) · render: service ID (srv-…, or crn-… for a cron job)").option("--skip-deploys", "railway: stage values without triggering a redeploy").option("--path <path>", "aws-parameter-store: hierarchy, e.g. /prod/storefront/ · aws-secrets-manager, azure-key-vault: name prefix").option("--layout <layout>", `aws-secrets-manager: ${AWS_SECRETS_MANAGER_LAYOUTS.join(" | ")}`).option("--secret-name <name>", "aws-secrets-manager: the secret a json-bundle writes to").option("--param-type <type>", `aws-parameter-store: ${AWS_PARAMETER_TYPES.join(" | ")}`).option("--tier <tier>", `aws-parameter-store: ${AWS_PARAMETER_TIERS.join(" | ")}`).option("--kms-key-id <id>", "aws: customer-managed KMS key id, ARN, or alias").option("--env-group <id>", "render: environment group ID (evg-…)").option("--fly-app <name>", "fly: app name, as `fly apps list` shows it").option("--secret-group <id>", "northflank: secret group ID (the slug in its URL)").option("--do-app <id>", "digitalocean: App Platform app ID (the UUID in its URL)").option("--component <name>", "digitalocean: write to one component's variables (omit for app-level)").option("--env-scope <scope>", `digitalocean: ${DIGITALOCEAN_ENV_SCOPES.join(" | ")}`).option("--heroku-app <name>", "heroku: app name, as `heroku apps` shows it (or its UUID)").option("--netlify-site <id>", "netlify: site API ID (the UUID under Project configuration)").option("--no-netlify-secret", "netlify: create readable variables instead of Netlify secrets (write-only)").option("--bunnyshell-environment <id>", "bunnyshell: environment ID (omit to write a project)").option("--no-bunnyshell-secret", "bunnyshell: create variables visible in the dashboard instead of secret ones").option("--gh-repo <owner/name>", "github-actions: repository, e.g. acme/storefront").option("--gh-environment <name>", "github-actions: write to one deployment environment's secrets (needs --gh-repo)").option("--gh-org <login>", "github-actions: write organization secrets instead of a repo's").option("--gh-visibility <v>", `github-actions org secrets: ${GITHUB_ACTIONS_VISIBILITIES.join(" | ")}`).option("--gh-repo-ids <ids>", "github-actions: comma-separated numeric repository IDs for --gh-visibility selected").option("--langgraph-deployment <id>", "langgraph-platform: deployment UUID (the one in its dashboard URL)").option("--gcp-prefix <prefix>", "gcp: prepended to every secret ID, e.g. prod-storefront-").option("--gcp-replication <policy>", `gcp: ${GCP_REPLICATION_POLICIES.join(" | ")}`, "automatic").option("--gcp-locations <list>", "gcp: regions for user-managed replication, e.g. us-east1").option("--gcp-kms-key <name>", "gcp: Cloud KMS key (projects/…/cryptoKeys/…)").option("--gcp-prune-versions", "gcp: destroy the version each push supersedes, keeping one active version").option("--vault <name>", "azure-key-vault: vault name, e.g. acme-prod").option("--name-mode <mode>", `azure-key-vault: ${AZURE_KEY_VAULT_NAME_MODES.join(" | ")} — Key Vault stores no underscores, so DATABASE_URL becomes DATABASE-URL unless you reject instead`);
13550
+ return command.option("--project <id>", "vercel: project id or name · cloudflare-pages / northflank: project name or slug · bunnyshell: project ID (writes variables new environments inherit)").option("--target <list>", "vercel / cloudflare-pages: comma-separated deployment targets", "production").option("--git-branch <branch>", "vercel: restrict preview writes to one branch").option("--script <name>", "cloudflare-workers: Worker script name").option("--store-id <id>", "cloudflare-secrets-store: store ID (32 hex)").option("--scopes <list>", "cloudflare-secrets-store: comma-separated scopes", "workers").option("--railway-project <id>", "railway: project ID (a UUID)").option("--railway-environment <id>", "railway: environment ID (a UUID)").option("--service <id>", "railway: service ID (omit for the environment's shared variables) · render: service ID (srv-…, or crn-… for a cron job)").option("--skip-deploys", "railway: stage values without triggering a redeploy").option("--path <path>", "aws-parameter-store: hierarchy, e.g. /prod/storefront/ · aws-secrets-manager, azure-key-vault: name prefix").option("--layout <layout>", `aws-secrets-manager: ${AWS_SECRETS_MANAGER_LAYOUTS.join(" | ")}`).option("--secret-name <name>", "aws-secrets-manager: the secret a json-bundle writes to").option("--param-type <type>", `aws-parameter-store: ${AWS_PARAMETER_TYPES.join(" | ")}`).option("--tier <tier>", `aws-parameter-store: ${AWS_PARAMETER_TIERS.join(" | ")}`).option("--kms-key-id <id>", "aws: customer-managed KMS key id, ARN, or alias").option("--env-group <id>", "render: environment group ID (evg-…)").option("--fly-app <name>", "fly: app name, as `fly apps list` shows it").option("--secret-group <id>", "northflank: secret group ID (the slug in its URL)").option("--do-app <id>", "digitalocean: App Platform app ID (the UUID in its URL)").option("--component <name>", "digitalocean: write to one component's variables (omit for app-level)").option("--env-scope <scope>", `digitalocean: ${DIGITALOCEAN_ENV_SCOPES.join(" | ")}`).option("--heroku-app <name>", "heroku: app name, as `heroku apps` shows it (or its UUID)").option("--netlify-site <id>", "netlify: site API ID (the UUID under Project configuration)").option("--no-netlify-secret", "netlify: create readable variables instead of Netlify secrets (write-only)").option("--bunnyshell-environment <id>", "bunnyshell: environment ID (omit to write a project)").option("--no-bunnyshell-secret", "bunnyshell: create variables visible in the dashboard instead of secret ones").option("--gh-repo <owner/name>", "github-actions: repository, e.g. acme/storefront").option("--gh-environment <name>", "github-actions: write to one deployment environment's secrets (needs --gh-repo)").option("--gh-org <login>", "github-actions: write organization secrets instead of a repo's").option("--gh-visibility <v>", `github-actions org secrets: ${GITHUB_ACTIONS_VISIBILITIES.join(" | ")}`).option("--gh-repo-ids <ids>", "github-actions: comma-separated numeric repository IDs for --gh-visibility selected").option("--langgraph-deployment <id>", "langgraph-platform: deployment UUID (the one in its dashboard URL)").option("--gcp-prefix <prefix>", "gcp: prepended to every secret ID, e.g. prod-storefront-").option("--gcp-replication <policy>", `gcp: ${GCP_REPLICATION_POLICIES.join(" | ")}`, "automatic").option("--gcp-locations <list>", "gcp: regions for user-managed replication, e.g. us-east1").option("--gcp-kms-key <name>", "gcp: Cloud KMS key (projects/…/cryptoKeys/…)").option("--gcp-prune-versions", "gcp: destroy the version each push supersedes, keeping one active version").option("--vault <name>", "azure-key-vault: vault name, e.g. acme-prod").option("--hf-space <owner/name>", "huggingface-spaces: Space ID, e.g. acme/support-demo (its owner and name, not a URL)").option("--name-mode <mode>", `azure-key-vault: ${AZURE_KEY_VAULT_NAME_MODES.join(" | ")} — Key Vault stores no underscores, so DATABASE_URL becomes DATABASE-URL unless you reject instead`);
13417
13551
  }
13418
13552
  /** Find a connection by id or name — nobody keeps `syc_…` ids in their head. */
13419
13553
  async function resolveConnection(ctx, orgId, ref) {
@@ -13860,9 +13994,16 @@ function parseVersion(raw) {
13860
13994
  if (!Number.isInteger(n) || n < 1) fail(`expected a positive whole number, got "${raw}"`);
13861
13995
  return n;
13862
13996
  }
13863
- /** Attach the environment-selection flags shared by every `secrets` command. */
13997
+ /**
13998
+ * Attach the environment-selection flags shared by every `secrets` command.
13999
+ *
14000
+ * `--env` is an ordinary option, not a required one: a service token is bound
14001
+ * to a single environment, so `seekrit secrets list` with nothing else on the
14002
+ * line is the whole invocation for CI and for agents. `resolveEnvTarget` fills
14003
+ * it in from the token, and still insists on it for a user session.
14004
+ */
13864
14005
  function withTarget(cmd) {
13865
- return cmd.option("--org <slug>", "organization slug").option("--app <slug>", "application slug (defaults to seekrit.json)").option("--group <slug>", "target a group environment instead of an app").option("--branch <slug>", "operate on a branch of --env").requiredOption("--env <slug>", "environment slug");
14006
+ return cmd.option("--org <slug>", "organization slug").option("--app <slug>", "application slug (defaults to seekrit.json)").option("--group <slug>", "target a group environment instead of an app").option("--branch <slug>", "operate on a branch of --env").option("--env <slug>", "environment slug (a service token infers its own)");
13866
14007
  }
13867
14008
  const secrets = program.command("secrets").description("manage secrets in an application or group environment");
13868
14009
  withTarget(secrets.command("list").alias("ls").description("list secret names (no values)").option("--json", "print the listing as JSON (metadata only — never values)")).action(async (options) => {
@@ -14320,7 +14461,7 @@ registerKmsCommands(program);
14320
14461
  registerRotationCommands(program);
14321
14462
  registerRecoveryCommands(program);
14322
14463
  program.command("mcp").description("run an MCP server over stdio so AI agents can drive seekrit").action(async () => {
14323
- const { runMcpServer } = await import("./mcp-DR-zla_u.js");
14464
+ const { runMcpServer } = await import("./mcp-BOawfW_U.js");
14324
14465
  await runMcpServer();
14325
14466
  });
14326
14467
  registerAuditCommands(program);
@@ -180,22 +180,6 @@ const targetShape = {
180
180
  group: z.string().optional().describe("target a group environment instead of an app"),
181
181
  env: z.string().optional().describe("environment slug or id (a service token infers its own)")
182
182
  };
183
- /**
184
- * Resolve which environment a secret tool addresses. A service token with no
185
- * explicit app/group targets its own bound environment (no flags needed);
186
- * everyone else names app|group + env.
187
- */
188
- async function resolveTargetEnv(ctx, o) {
189
- if (isTokenAuth(ctx) && !o.app && !o.group) {
190
- const { scope } = await ctx.client.resolve();
191
- return {
192
- orgId: scope.orgId,
193
- envId: scope.envId,
194
- label: `${scope.appSlug}/${scope.envSlug}`
195
- };
196
- }
197
- return resolveEnvTarget(ctx, o);
198
- }
199
183
  async function runMcpServer(options = {}) {
200
184
  setFailThrows(true);
201
185
  await ensureM2mAdminToken();
@@ -476,7 +460,7 @@ async function runMcpServer(options = {}) {
476
460
  });
477
461
  tool("list_secrets", "List secret names + versions in an environment (never values).", ro, targetShape, async (o) => {
478
462
  const ctx = getCtx();
479
- const { orgId, envId } = await resolveTargetEnv(ctx, o);
463
+ const { orgId, envId } = await resolveEnvTarget(ctx, o);
480
464
  const { secrets } = await ctx.client.listSecrets(orgId, envId);
481
465
  return secrets.map((s) => ({
482
466
  name: s.name,
@@ -665,7 +649,7 @@ async function runMcpServer(options = {}) {
665
649
  }, async (o) => {
666
650
  const ctx = getCtx();
667
651
  ensureDecryptable(ctx);
668
- const { orgId, envId } = await resolveTargetEnv(ctx, o);
652
+ const { orgId, envId } = await resolveEnvTarget(ctx, o);
669
653
  await encryptAndSetSecret(ctx, orgId, envId, o.name, o.value);
670
654
  return {
671
655
  ok: true,
@@ -680,7 +664,7 @@ async function runMcpServer(options = {}) {
680
664
  version: z.number().int().positive().optional().describe("an earlier version from list_secret_versions (default: current)")
681
665
  }, async (o) => {
682
666
  const ctx = getCtx();
683
- const { orgId, envId } = await resolveTargetEnv(ctx, o);
667
+ const { orgId, envId } = await resolveEnvTarget(ctx, o);
684
668
  if (!o.reveal) {
685
669
  const { secrets } = await ctx.client.listSecrets(orgId, envId);
686
670
  const row = secrets.find((s) => s.name === o.name);
@@ -715,7 +699,7 @@ async function runMcpServer(options = {}) {
715
699
  limit: z.number().int().min(1).max(200).optional().describe("default 20")
716
700
  }, async (o) => {
717
701
  const ctx = getCtx();
718
- const { orgId, envId } = await resolveTargetEnv(ctx, o);
702
+ const { orgId, envId } = await resolveEnvTarget(ctx, o);
719
703
  const { versions, currentVersion } = await ctx.client.listSecretVersions(orgId, envId, o.name, { limit: o.limit ?? 20 });
720
704
  return {
721
705
  currentVersion,
@@ -733,7 +717,7 @@ async function runMcpServer(options = {}) {
733
717
  version: z.number().int().positive()
734
718
  }, async (o) => {
735
719
  const ctx = getCtx();
736
- const { orgId, envId } = await resolveTargetEnv(ctx, o);
720
+ const { orgId, envId } = await resolveEnvTarget(ctx, o);
737
721
  const { secret, restoredFrom } = await ctx.client.restoreSecret(orgId, envId, o.name, o.version);
738
722
  return {
739
723
  ok: true,
@@ -747,7 +731,7 @@ async function runMcpServer(options = {}) {
747
731
  name: z.string()
748
732
  }, async (o) => {
749
733
  const ctx = getCtx();
750
- const { orgId, envId } = await resolveTargetEnv(ctx, o);
734
+ const { orgId, envId } = await resolveEnvTarget(ctx, o);
751
735
  await ctx.client.deleteSecret(orgId, envId, o.name);
752
736
  return {
753
737
  ok: true,
@@ -870,7 +854,7 @@ async function runMcpServer(options = {}) {
870
854
  if (Boolean(o.user) === Boolean(o.token)) throw new Error("pass exactly one of user or token");
871
855
  const ctx = getCtx();
872
856
  ensureDecryptable(ctx);
873
- const { orgId, envId, label } = await resolveTargetEnv(ctx, o);
857
+ const { orgId, envId, label } = await resolveEnvTarget(ctx, o);
874
858
  const dek = await getDek(ctx, orgId, envId);
875
859
  let principalType;
876
860
  let principalId;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seekrit/cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.1",
4
4
  "description": "End-to-end encrypted secrets manager CLI — inject decrypted secrets into any command.",
5
5
  "type": "module",
6
6
  "publishConfig": {
@@ -31,8 +31,8 @@
31
31
  "tsdown": "^0.22.3",
32
32
  "vitest": "^4.1.9",
33
33
  "@seekrit/api-client": "0.0.1",
34
- "@seekrit/crypto": "0.0.1",
35
- "@seekrit/core": "0.0.1"
34
+ "@seekrit/core": "0.0.1",
35
+ "@seekrit/crypto": "0.0.1"
36
36
  },
37
37
  "scripts": {
38
38
  "build": "tsdown",