@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 +6 -5
- package/dist/index.js +149 -8
- package/dist/{mcp-DR-zla_u.js → mcp-BOawfW_U.js} +7 -23
- package/package.json +3 -3
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
|
|
34
|
-
#
|
|
35
|
-
seekrit init --org acme --app storefront
|
|
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
|
|
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
|
|
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
|
-
/**
|
|
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").
|
|
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-
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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/
|
|
35
|
-
"@seekrit/
|
|
34
|
+
"@seekrit/core": "0.0.1",
|
|
35
|
+
"@seekrit/crypto": "0.0.1"
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "tsdown",
|