@vaultic-dev/cli 0.1.0 → 0.1.2

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.
Files changed (3) hide show
  1. package/README.md +63 -0
  2. package/dist/index.js +186 -84
  3. package/package.json +10 -3
package/README.md ADDED
@@ -0,0 +1,63 @@
1
+ # @vaultic-dev/cli
2
+
3
+ Command-line client for [Vaultic](https://github.com/vaultic-dev/vaultic) — encrypted,
4
+ versioned secrets management with environment inheritance, rotation, and audit logging. This
5
+ package installs the `vaultic` binary; it talks to a Vaultic server over the REST API and never
6
+ handles crypto itself beyond decrypting values already fetched over the wire.
7
+
8
+ ## Install
9
+
10
+ ```bash
11
+ npm install -g @vaultic-dev/cli
12
+ vaultic --help
13
+ ```
14
+
15
+ Requires Node.js >= 20. You'll also need a Vaultic server to talk to — either your own
16
+ self-hosted instance, or one your team runs (see the
17
+ [main repo README](https://github.com/vaultic-dev/vaultic#readme) for self-hosting instructions).
18
+
19
+ ## Quick start
20
+
21
+ ```bash
22
+ vaultic login # opens a browser to approve the login (--no-browser for the
23
+ # email/password prompt, --token to paste one)
24
+ vaultic init # interactive: pick/create workspace -> project -> default env
25
+ # writes .vaultic.yaml in the current directory
26
+
27
+ vaultic secrets set FOO bar # stored encrypted server-side, versioned
28
+ vaultic secrets list # masked by default
29
+ vaultic secrets list --reveal # explicit reveal, audited separately from list/read
30
+
31
+ vaultic run -- printenv FOO # injects secrets as env vars into the subprocess -> "bar"
32
+ vaultic export # writes .env (dotenv format) with a checksum header
33
+ vaultic status # compares local .env vs the server
34
+ vaultic sync # pulls latest values, regenerates .env, runs post_sync hook
35
+ ```
36
+
37
+ By default, `vaultic --server <url>` (or the `VAULTIC_API_URL` env var) points at your server;
38
+ otherwise it defaults to `http://localhost:4000`.
39
+
40
+ ## Command reference
41
+
42
+ | Group | Commands |
43
+ |---|---|
44
+ | Auth | `login`, `logout`, `whoami` |
45
+ | Project setup | `init` (writes `.vaultic.yaml`) |
46
+ | Secrets | `secrets list/get/set/delete/history/rollback/rename/rotate`, `secrets override set/clear`, `rotation-providers` |
47
+ | Local file sync | `run`, `export`, `import`, `status`, `sync` |
48
+ | Environments | `env list/create/duplicate/diff/promote/lock/unlock/proposals/approve/reject` |
49
+ | Workspace | `workspace invite/invites/revoke-invite/members/set-role/remove-member/accept-invite` |
50
+ | Access grants | `access grant/list/revoke` |
51
+ | Service tokens | `tokens create/list/revoke` |
52
+ | Sharing | `share <key>` — one-time/limited-view unauthenticated link |
53
+ | Webhooks | `webhooks create/list/delete/deliveries` |
54
+ | Third-party push | `push github`, `push vercel` |
55
+ | Git integration | `git-integration setup/show/remove` — ephemeral preview environments |
56
+
57
+ Run `vaultic <command> --help` for a command's full options.
58
+
59
+ ## Links
60
+
61
+ - [Full documentation, deployment guide, and security model](https://github.com/vaultic-dev/vaultic#readme)
62
+ - [Changelog](https://github.com/vaultic-dev/vaultic/blob/main/CHANGELOG.md)
63
+ - [Issues](https://github.com/vaultic-dev/vaultic/issues)
package/dist/index.js CHANGED
@@ -100,6 +100,11 @@ import { z } from "zod";
100
100
  var SECRET_KEY_REGEX = /^[A-Z][A-Z0-9_]*$/;
101
101
  var SlugSchema = z.string().min(1).max(64).regex(/^[a-z0-9][a-z0-9-]*$/, "must be lowercase alphanumeric with dashes");
102
102
  var SecretKeySchema = z.string().min(1).max(256).regex(SECRET_KEY_REGEX, "keys must be UPPER_SNAKE_CASE (e.g. DATABASE_URL)");
103
+ function normalizeTag(tag) {
104
+ return tag.trim().toLowerCase();
105
+ }
106
+ var TagSchema = z.string().trim().min(1, "tag cannot be empty").max(64, "tag must be 64 characters or fewer").transform(normalizeTag);
107
+ var SecretTagsSchema = z.array(TagSchema).max(50, "a secret can have at most 50 tags").transform((tags) => Array.from(new Set(tags)));
103
108
  var RoleSchema = z.enum(["owner", "admin", "developer", "read-only"]);
104
109
  var TokenAccessSchema = z.enum(["read", "write"]);
105
110
  var SecretTypeHintSchema = z.enum([
@@ -227,7 +232,14 @@ var SecretSummarySchema = z.object({
227
232
  /** when this secret's value was last changed — `rotatedAt` if it's ever gone through
228
233
  * `rotate`, otherwise the most recent version's createdAt. Used to compute rotation-due
229
234
  * state (see `isRotationDue`) client-side, in the CLI, and by the scheduled lifecycle job. */
230
- lastRotatedAt: z.string().nullable().optional()
235
+ lastRotatedAt: z.string().nullable().optional(),
236
+ /** freeform, normalized (lowercase + trim) tags assigned to this secret (spec: "Tags on
237
+ * secrets + tag-based search"). Visible to anyone who can see the secret at all — not
238
+ * sensitive, same as key names. */
239
+ tags: z.array(z.string()).optional(),
240
+ /** number of entries in this secret's comment thread (see SecretComment) — surfaced on the
241
+ * row so the comment/mention feature is discoverable without opening the panel first. */
242
+ commentCount: z.number().int().optional()
231
243
  });
232
244
  var SecretRevealedSchema = SecretSummarySchema.extend({
233
245
  value: z.string()
@@ -259,6 +271,20 @@ var RotateSecretRequestSchema = z.object({
259
271
  * `secrets get KEY --previous`. Omit for no time limit. */
260
272
  graceWindow: z.string().optional()
261
273
  });
274
+ var ExecuteRotationRequestSchema = z.object({
275
+ /** rotation provider id, e.g. "postgres" — must be one the server implements */
276
+ provider: z.string(),
277
+ /** the secret's current (pre-rotation) resolved value, e.g. a connection string */
278
+ currentValue: z.string(),
279
+ /** provider-specific flags, e.g. { role, adminConnString, newPassword } for postgres */
280
+ options: z.record(z.string().optional()).optional()
281
+ });
282
+ var ExecuteRotationResponseSchema = z.object({
283
+ /** the new value to store as the secret's next version */
284
+ newValue: z.string(),
285
+ /** one-line human-readable description of what was rotated, printed by the CLI */
286
+ summary: z.string()
287
+ });
262
288
  function parseDurationMs(spec) {
263
289
  const match = /^(\d+)(s|m|h|d)$/.exec(spec.trim());
264
290
  if (!match)
@@ -315,6 +341,38 @@ var AuditLogEntrySchema = z.object({
315
341
  ip: z.string().nullable(),
316
342
  createdAt: z.string()
317
343
  });
344
+ var NotificationSchema = z.object({
345
+ id: z.string(),
346
+ type: z.string(),
347
+ refId: z.string().nullable(),
348
+ refType: z.string().nullable(),
349
+ data: z.record(z.string(), z.unknown()).nullable(),
350
+ read: z.boolean(),
351
+ readAt: z.string().nullable(),
352
+ createdAt: z.string()
353
+ });
354
+ var NotificationListResponseSchema = z.object({
355
+ notifications: z.array(NotificationSchema),
356
+ unreadCount: z.number()
357
+ });
358
+ var SecretCommentSchema = z.object({
359
+ id: z.string(),
360
+ secretId: z.string(),
361
+ authorId: z.string().nullable(),
362
+ authorName: z.string().nullable(),
363
+ authorEmail: z.string().nullable(),
364
+ body: z.string(),
365
+ createdAt: z.string(),
366
+ /** null until the comment has been edited at least once */
367
+ editedAt: z.string().nullable()
368
+ });
369
+ var SecretCommentBodySchema = z.string().trim().min(1, "comment cannot be empty").max(4e3, "comment must be 4000 characters or fewer");
370
+ var CreateSecretCommentRequestSchema = z.object({
371
+ body: SecretCommentBodySchema
372
+ });
373
+ var UpdateSecretCommentRequestSchema = z.object({
374
+ body: SecretCommentBodySchema
375
+ });
318
376
  var RegisterRequestSchema = z.object({
319
377
  email: z.string().email(),
320
378
  password: z.string().min(8),
@@ -377,11 +435,17 @@ var SetSecretRequestSchema = z.object({
377
435
  expiresAt: z.string().datetime().nullable().optional(),
378
436
  /** omit to leave unchanged, null to clear, a positive integer (days) to set (see
379
437
  * "Rotation reminders" in the spec) */
380
- rotationReminderIntervalDays: z.number().int().positive().nullable().optional()
438
+ rotationReminderIntervalDays: z.number().int().positive().nullable().optional(),
439
+ /** omit to leave tags unchanged, an array (possibly empty, to clear all tags) to replace
440
+ * the full tag set — normalized/deduped by SecretTagsSchema (see "Tags on secrets"). */
441
+ tags: SecretTagsSchema.optional()
381
442
  });
382
443
  var UpdateSecretMetadataRequestSchema = z.object({
383
444
  expiresAt: z.string().datetime().nullable().optional(),
384
- rotationReminderIntervalDays: z.number().int().positive().nullable().optional()
445
+ rotationReminderIntervalDays: z.number().int().positive().nullable().optional(),
446
+ /** omit to leave tags unchanged, an array (possibly empty, to clear all tags) to replace
447
+ * the full tag set. */
448
+ tags: SecretTagsSchema.optional()
385
449
  });
386
450
  var RenameSecretRequestSchema = z.object({
387
451
  newKey: SecretKeySchema
@@ -558,6 +622,20 @@ var SecretsHealthSchema = z.object({
558
622
  expiredReminders: z.array(ExpiredReminderSchema),
559
623
  inactiveConfigs: z.array(InactiveConfigSchema)
560
624
  });
625
+ var DuplicateValueMemberSchema = SecretsHealthLocationSchema.extend({
626
+ key: z.string()
627
+ });
628
+ var DuplicateValueGroupSchema = z.object({
629
+ count: z.number().int(),
630
+ members: z.array(DuplicateValueMemberSchema)
631
+ });
632
+ var DuplicateValuesResponseSchema = z.object({
633
+ groups: z.array(DuplicateValueGroupSchema),
634
+ /** true if at least one visible secret's current version hasn't been HMAC-indexed yet
635
+ * (null valueHmac) — e.g. a version written before this feature existed. Lets the UI show
636
+ * a "results may be incomplete" notice instead of silently under-reporting duplicates. */
637
+ incomplete: z.boolean()
638
+ });
561
639
 
562
640
  // src/lib/project-config.ts
563
641
  var CONFIG_FILE = ".vaultic.yaml";
@@ -724,6 +802,88 @@ function exitCancelled() {
724
802
 
725
803
  // src/commands/auth.ts
726
804
  import prompts2 from "prompts";
805
+
806
+ // src/lib/browser-login.ts
807
+ import { createServer } from "http";
808
+ import { randomBytes } from "crypto";
809
+ import { spawn } from "child_process";
810
+ function openInBrowser(url) {
811
+ try {
812
+ const platform = process.platform;
813
+ const [cmd, args] = platform === "darwin" ? ["open", [url]] : platform === "win32" ? ["cmd", ["/c", "start", '""', url]] : ["xdg-open", [url]];
814
+ spawn(cmd, args, { stdio: "ignore", detached: true }).unref();
815
+ } catch {
816
+ }
817
+ }
818
+ var DEFAULT_TIMEOUT_MS = 5 * 60 * 1e3;
819
+ async function browserLogin(opts) {
820
+ const { serverUrl, timeoutMs = DEFAULT_TIMEOUT_MS, onUrl } = opts;
821
+ const providers = await fetch(`${serverUrl}/auth/providers`).then((res) => res.ok ? res.json() : null).catch(() => null);
822
+ const webAppUrl = providers?.webAppUrl;
823
+ if (!webAppUrl) {
824
+ throw new Error(
825
+ "This server doesn't support browser login (no webAppUrl from /auth/providers) -- use --email/--password or --token instead."
826
+ );
827
+ }
828
+ const state = randomBytes(24).toString("hex");
829
+ return new Promise((resolve, reject) => {
830
+ let settled = false;
831
+ const timeout = setTimeout(() => {
832
+ if (settled) return;
833
+ settled = true;
834
+ server.close();
835
+ reject(new Error("Timed out waiting for browser login approval."));
836
+ }, timeoutMs);
837
+ const server = createServer((req, res) => {
838
+ const url = new URL(req.url ?? "/", "http://127.0.0.1");
839
+ if (url.pathname !== "/callback") {
840
+ res.writeHead(404).end();
841
+ return;
842
+ }
843
+ const returnedState = url.searchParams.get("state");
844
+ const code = url.searchParams.get("code");
845
+ const finish = (err, result) => {
846
+ if (settled) return;
847
+ settled = true;
848
+ clearTimeout(timeout);
849
+ server.close();
850
+ if (err) reject(err);
851
+ else resolve(result);
852
+ };
853
+ if (returnedState !== state || !code) {
854
+ res.writeHead(400, { "content-type": "text/html" }).end("<h1>Invalid login attempt.</h1>You can close this tab.");
855
+ finish(new Error("Received an invalid or mismatched login callback."));
856
+ return;
857
+ }
858
+ fetch(`${serverUrl}/auth/cli/exchange`, {
859
+ method: "POST",
860
+ headers: { "content-type": "application/json" },
861
+ body: JSON.stringify({ code })
862
+ }).then(async (exchangeRes) => {
863
+ const data = await exchangeRes.json();
864
+ if (!exchangeRes.ok) throw new Error(data?.error ?? "Login failed");
865
+ res.writeHead(200, { "content-type": "text/html" }).end("<h1>Logged in!</h1>You can close this tab and return to your terminal.");
866
+ finish(null, data);
867
+ }).catch((err) => {
868
+ res.writeHead(500, { "content-type": "text/html" }).end("<h1>Login failed.</h1>You can close this tab.");
869
+ finish(err instanceof Error ? err : new Error(String(err)));
870
+ });
871
+ });
872
+ server.listen(0, "127.0.0.1", () => {
873
+ const port = server.address().port;
874
+ const loginUrl = `${webAppUrl}/cli-login?state=${state}&port=${port}`;
875
+ if (onUrl) {
876
+ onUrl(loginUrl);
877
+ } else {
878
+ console.log(`Opening ${loginUrl} in your browser\u2026`);
879
+ console.log("Waiting for you to approve the login (Ctrl+C to cancel)\u2026");
880
+ openInBrowser(loginUrl);
881
+ }
882
+ });
883
+ });
884
+ }
885
+
886
+ // src/commands/auth.ts
727
887
  async function loginCommand(opts) {
728
888
  const serverUrl = opts.server ?? process.env.VAULTIC_API_URL ?? DEFAULT_SERVER_URL;
729
889
  if (opts.token) {
@@ -731,6 +891,12 @@ async function loginCommand(opts) {
731
891
  console.log(`Saved credentials for ${serverUrl}`);
732
892
  return;
733
893
  }
894
+ if (!opts.email && !opts.password && opts.browser !== false) {
895
+ const res = await browserLogin({ serverUrl });
896
+ saveCredentials({ serverUrl, token: res.token, email: res.user.email });
897
+ console.log(`Logged in as ${res.user.email}`);
898
+ return;
899
+ }
734
900
  const email = opts.email ?? (await prompts2({ type: "text", name: "email", message: "Email" })).email;
735
901
  const password = opts.password ?? (await prompts2({ type: "password", name: "password", message: "Password" })).password;
736
902
  if (!email || !password) {
@@ -766,7 +932,7 @@ async function whoamiCommand() {
766
932
  console.log("Not logged in.");
767
933
  return;
768
934
  }
769
- if (creds.token.startsWith("vlt_")) {
935
+ if (creds.token.startsWith("sht_")) {
770
936
  console.log(`Authenticated with a service token against ${creds.serverUrl}`);
771
937
  return;
772
938
  }
@@ -776,7 +942,7 @@ async function whoamiCommand() {
776
942
  }
777
943
 
778
944
  // src/commands/run.ts
779
- import { spawn } from "child_process";
945
+ import { spawn as spawn2 } from "child_process";
780
946
 
781
947
  // src/lib/context.ts
782
948
  function envBase(cfg, env2) {
@@ -802,7 +968,7 @@ async function runCommand(commandParts, opts) {
802
968
  const fetchSecrets = () => api.get(`${envBase(cfg, env2)}/export`);
803
969
  if (!opts.watch) {
804
970
  const secrets2 = await fetchSecrets();
805
- const child2 = spawn(cmd, args, { stdio: "inherit", env: { ...process.env, ...secrets2 } });
971
+ const child2 = spawn2(cmd, args, { stdio: "inherit", env: { ...process.env, ...secrets2 } });
806
972
  child2.on("exit", (code, signal) => {
807
973
  if (signal) process.kill(process.pid, signal);
808
974
  else process.exit(code ?? 0);
@@ -819,7 +985,7 @@ async function runCommand(commandParts, opts) {
819
985
  let stopped = false;
820
986
  let restarting = false;
821
987
  function spawnChild() {
822
- child = spawn(cmd, args, { stdio: "inherit", env: { ...process.env, ...current } });
988
+ child = spawn2(cmd, args, { stdio: "inherit", env: { ...process.env, ...current } });
823
989
  child.on("exit", (code, signal) => {
824
990
  if (stopped || restarting) return;
825
991
  stopped = true;
@@ -1094,89 +1260,23 @@ async function syncCommand(opts) {
1094
1260
  import readline from "readline";
1095
1261
 
1096
1262
  // src/rotation/postgres.ts
1097
- import { randomBytes } from "crypto";
1098
- import { Client } from "pg";
1099
- var ROLE_NAME_REGEX = /^[A-Za-z_][A-Za-z0-9_]*$/;
1100
- function generatePassword(bytes = 24) {
1101
- return randomBytes(bytes).toString("base64url");
1102
- }
1103
- function sqlLiteral(value) {
1104
- return `'${value.replace(/'/g, "''")}'`;
1105
- }
1106
1263
  var postgresRotationPlugin = {
1107
1264
  name: "postgres",
1108
1265
  describe() {
1109
- return "Rotates a PostgreSQL role's password via `ALTER ROLE ... WITH PASSWORD ...`. Treats the secret's current value as a postgres://user:password@host:port/db connection string. Options: --role <name> (defaults to the connection string's user), --admin-conn-string <url> (connect with different credentials than the value being rotated, e.g. a superuser, when the target role can't alter its own password), --new-password <value> (defaults to a random 32-character password).";
1266
+ return "Rotates a PostgreSQL role's password via `ALTER ROLE ... WITH PASSWORD ...`, executed server-side (the CLI never connects to the database directly). Treats the secret's current value as a postgres://user:password@host:port/db connection string. Options: --role <name> (defaults to the connection string's user), --admin-conn-string <url> (connect with different credentials than the value being rotated, e.g. a superuser, when the target role can't alter its own password), --new-password <value> (defaults to a random 32-character password).";
1110
1267
  },
1111
1268
  async rotate(ctx) {
1112
- let targetUrl;
1113
- try {
1114
- targetUrl = new URL(ctx.currentValue);
1115
- } catch {
1116
- throw new Error(
1117
- `Current value for "${ctx.key}" isn't a valid URL \u2014 expected a postgres://user:password@host:port/db connection string.`
1118
- );
1119
- }
1120
- if (targetUrl.protocol !== "postgres:" && targetUrl.protocol !== "postgresql:") {
1121
- throw new Error(
1122
- `Current value for "${ctx.key}" has scheme "${targetUrl.protocol}", expected postgres:// or postgresql://.`
1123
- );
1124
- }
1125
- const role = ctx.options.role ?? decodeURIComponent(targetUrl.username);
1126
- if (!role) throw new Error(`Could not determine the role to rotate for "${ctx.key}"; pass --role explicitly.`);
1127
- if (!ROLE_NAME_REGEX.test(role)) {
1128
- throw new Error(`Refusing to rotate role "${role}": expected [A-Za-z_][A-Za-z0-9_]* (use --role to override).`);
1129
- }
1130
- const connectionString = ctx.options.adminConnString ?? ctx.currentValue;
1131
- const newPassword = ctx.options.newPassword ?? generatePassword();
1132
- const client = new Client({ connectionString });
1133
- await client.connect();
1134
- try {
1135
- await client.query(`ALTER ROLE "${role}" WITH PASSWORD ${sqlLiteral(newPassword)}`);
1136
- } finally {
1137
- await client.end();
1138
- }
1139
- const newUrl = new URL(targetUrl.toString());
1140
- newUrl.password = newPassword;
1141
- return {
1142
- newValue: newUrl.toString(),
1143
- summary: `Rotated PostgreSQL role "${role}"'s password (ALTER ROLE) on ${targetUrl.hostname}:${targetUrl.port || "5432"}/${targetUrl.pathname.replace(/^\//, "")}`
1144
- };
1145
- }
1146
- };
1147
-
1148
- // src/rotation/types.ts
1149
- var RotationNotImplementedError = class extends Error {
1150
- };
1151
-
1152
- // src/rotation/stripe.ts
1153
- var stripeRotationPlugin = {
1154
- name: "stripe",
1155
- describe() {
1156
- return "STUB \u2014 not exercised against a live Stripe account in this environment. Documents the interface for restricted-key rotation (create new key with same scopes, store it, revoke the old one after the grace window). See packages/cli/src/rotation/stripe.ts for what a real implementation needs.";
1157
- },
1158
- async rotate() {
1159
- throw new RotationNotImplementedError(
1160
- "The stripe rotation plugin is a documented stub (no Stripe credentials available in this environment to build/verify a real integration) \u2014 see `packages/cli/src/rotation/stripe.ts`."
1161
- );
1162
- }
1163
- };
1164
-
1165
- // src/rotation/aws.ts
1166
- var awsIamRotationPlugin = {
1167
- name: "aws-iam",
1168
- describe() {
1169
- return "STUB \u2014 not exercised against a live AWS account in this environment. Documents the interface for IAM access-key rotation (CreateAccessKey for a new key while the old one stays active, then deactivate/delete the old key after the grace window). See packages/cli/src/rotation/aws.ts for what a real implementation needs.";
1170
- },
1171
- async rotate() {
1172
- throw new RotationNotImplementedError(
1173
- "The aws-iam rotation plugin is a documented stub (no AWS credentials available in this environment to build/verify a real integration) \u2014 see `packages/cli/src/rotation/aws.ts`."
1174
- );
1269
+ const response = await ctx.api.post(`${ctx.envBase}/secrets/${ctx.key}/rotate/execute`, {
1270
+ provider: "postgres",
1271
+ currentValue: ctx.currentValue,
1272
+ options: ctx.options
1273
+ });
1274
+ return { newValue: response.newValue, summary: response.summary };
1175
1275
  }
1176
1276
  };
1177
1277
 
1178
1278
  // src/rotation/index.ts
1179
- var PLUGINS = [postgresRotationPlugin, stripeRotationPlugin, awsIamRotationPlugin];
1279
+ var PLUGINS = [postgresRotationPlugin];
1180
1280
  function getRotationPlugin(name) {
1181
1281
  const plugin = PLUGINS.find((p) => p.name === name);
1182
1282
  if (!plugin) {
@@ -1294,7 +1394,9 @@ async function secretsRotateCommand(key, value, opts) {
1294
1394
  const result = await plugin.rotate({
1295
1395
  key,
1296
1396
  currentValue: current.value,
1297
- options: { role: opts.role, adminConnString: opts.adminConnString, newPassword: opts.newPassword }
1397
+ options: { role: opts.role, adminConnString: opts.adminConnString, newPassword: opts.newPassword },
1398
+ api,
1399
+ envBase: envBase(cfg, env2)
1298
1400
  });
1299
1401
  console.log(result.summary);
1300
1402
  resolvedValue = result.newValue;
@@ -1956,7 +2058,7 @@ async function gitIntegrationRemoveCommand() {
1956
2058
  var program = new Command();
1957
2059
  program.name("vaultic").description("Vaultic CLI").version("0.1.0");
1958
2060
  program.command("init").description("Initialize this directory as a Vaultic project (writes .vaultic.yaml)").action(wrap(initCommand));
1959
- program.command("login").description("Log in with email/password, or paste a token with --token").option("--token <token>", "paste a session or service token instead of logging in interactively").option("--server <url>", "Vaultic server URL").option("--email <email>", "email (non-interactive)").option("--password <password>", "password (non-interactive)").action(wrap(loginCommand));
2061
+ program.command("login").description("Log in via your browser (default), with email/password, or by pasting a token with --token").option("--token <token>", "paste a session or service token instead of logging in interactively").option("--server <url>", "Vaultic server URL").option("--email <email>", "email (non-interactive; skips the browser flow)").option("--password <password>", "password (non-interactive; skips the browser flow)").option("--no-browser", "skip opening a browser; prompt for email/password instead").action(wrap(loginCommand));
1960
2062
  program.command("logout").description("Clear stored credentials").action(wrap(logoutCommand));
1961
2063
  program.command("whoami").description("Show the current logged-in user").action(wrap(whoamiCommand));
1962
2064
  program.command("run").description("Inject secrets as env vars and run a command").option("-e, --env <environment>", "environment to use").option("--watch", "poll for secret changes and restart the subprocess when they change").option("--poll-interval <ms>", "watch poll interval in milliseconds (default 2000)").allowUnknownOption().argument("<command...>", "command to run, after --").action(wrap((commandParts, opts) => runCommand(commandParts, opts)));
@@ -1972,7 +2074,7 @@ secrets.command("delete").argument("<key>").option("-e, --env <environment>").ac
1972
2074
  secrets.command("history").argument("<key>").option("-e, --env <environment>").option("--reveal").action(wrap(secretsHistoryCommand));
1973
2075
  secrets.command("rollback").argument("<key>").requiredOption("--version <version>").option("-e, --env <environment>").action(wrap(secretsRollbackCommand));
1974
2076
  secrets.command("rename").argument("<oldKey>").argument("<newKey>").option("-e, --env <environment>").action(wrap(secretsRenameCommand));
1975
- secrets.command("rotate").description("Set a new value, keeping the old one fetchable via 'secrets get KEY --previous'").argument("<key>").argument("[value]", "new value, or '-' to read from stdin (omit entirely when using --provider)").option("-e, --env <environment>").option("--from-file <path>", "read the new value from a file").option("--grace <duration>", 'how long the previous value stays fetchable, e.g. "1h", "30m" (omit for no limit)').option("--provider <name>", "have a rotation plugin compute the new value (postgres | stripe | aws-iam)").option("--role <role>", "[postgres] role to rotate (defaults to the connection string's user)").option("--admin-conn-string <url>", "[postgres] connect with different credentials than the value being rotated").option("--new-password <password>", "[postgres] use this password instead of a random one").action(wrap(secretsRotateCommand));
2077
+ secrets.command("rotate").description("Set a new value, keeping the old one fetchable via 'secrets get KEY --previous'").argument("<key>").argument("[value]", "new value, or '-' to read from stdin (omit entirely when using --provider)").option("-e, --env <environment>").option("--from-file <path>", "read the new value from a file").option("--grace <duration>", 'how long the previous value stays fetchable, e.g. "1h", "30m" (omit for no limit)').option("--provider <name>", "have a rotation plugin compute the new value (postgres)").option("--role <role>", "[postgres] role to rotate (defaults to the connection string's user)").option("--admin-conn-string <url>", "[postgres] connect with different credentials than the value being rotated").option("--new-password <password>", "[postgres] use this password instead of a random one").action(wrap(secretsRotateCommand));
1976
2078
  program.command("rotation-providers").description("List available `secrets rotate --provider` plugins").action(wrap(rotationProvidersCommand));
1977
2079
  var override = secrets.command("override").description("Manage your personal, server-side value overrides");
1978
2080
  override.command("set").argument("<key>").argument("[value]", "value, or '-' to read from stdin").option("-e, --env <environment>").option("--from-file <path>", "read the value from a file").action(wrap(secretsOverrideSetCommand));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vaultic-dev/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "vaultic": "./dist/index.js"
@@ -11,6 +11,15 @@
11
11
  "engines": {
12
12
  "node": ">=20"
13
13
  },
14
+ "repository": {
15
+ "type": "git",
16
+ "url": "git+https://github.com/vaultic-dev/vaultic.git",
17
+ "directory": "packages/cli"
18
+ },
19
+ "homepage": "https://github.com/vaultic-dev/vaultic#readme",
20
+ "bugs": {
21
+ "url": "https://github.com/vaultic-dev/vaultic/issues"
22
+ },
14
23
  "publishConfig": {
15
24
  "access": "public"
16
25
  },
@@ -26,7 +35,6 @@
26
35
  "commander": "^12.1.0",
27
36
  "js-yaml": "^4.1.0",
28
37
  "libsodium-wrappers": "^0.8.4",
29
- "pg": "^8.13.1",
30
38
  "prompts": "^2.4.2",
31
39
  "zod": "^3.23.8"
32
40
  },
@@ -34,7 +42,6 @@
34
42
  "@types/js-yaml": "^4.0.9",
35
43
  "@types/libsodium-wrappers": "^0.8.2",
36
44
  "@types/node": "^22.7.5",
37
- "@types/pg": "^8.11.10",
38
45
  "@types/prompts": "^2.4.9",
39
46
  "tsup": "^8.3.0",
40
47
  "tsx": "^4.19.1",