@envpilot/cli 1.5.0 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -4,7 +4,7 @@ import {
4
4
  getTopLevelCommandCatalog,
5
5
  getUser,
6
6
  isAuthenticated
7
- } from "./chunk-CIIQSYAS.js";
7
+ } from "./chunk-RUYGAIH5.js";
8
8
 
9
9
  // src/ui/app.tsx
10
10
  import {
@@ -7,7 +7,7 @@ function initSentry() {
7
7
  Sentry.init({
8
8
  dsn,
9
9
  environment: "cli",
10
- release: true ? "1.5.0" : "0.0.0",
10
+ release: true ? "1.6.0" : "0.0.0",
11
11
  // Free tier: disable performance monitoring
12
12
  tracesSampleRate: 0,
13
13
  beforeSend(event) {
@@ -157,7 +157,7 @@ import { jsx } from "react/jsx-runtime";
157
157
  async function openTUI() {
158
158
  const [{ render }, { CLIApp }] = await Promise.all([
159
159
  import("ink"),
160
- import("./app-KHDRTCOB.js")
160
+ import("./app-UJRUWQKL.js")
161
161
  ]);
162
162
  while (true) {
163
163
  let selectedArgv = null;
@@ -429,6 +429,9 @@ function notInitialized() {
429
429
  function fileNotFound(path) {
430
430
  return new CLIError(`File not found: ${path}`, ErrorCodes.FILE_NOT_FOUND);
431
431
  }
432
+ function invalidInput(message) {
433
+ return new CLIError(message, ErrorCodes.INVALID_INPUT);
434
+ }
432
435
 
433
436
  // src/lib/auth-flow.ts
434
437
  import open from "open";
@@ -673,7 +676,12 @@ var APIClient = class {
673
676
  return this.get(`/api/cli/projects/${projectId}`);
674
677
  }
675
678
  /**
676
- * List variables in a project
679
+ * List variables in a project (with decrypted values).
680
+ *
681
+ * Returns both the variable list and any keys that failed vault decryption.
682
+ * Decryption failures are skipped server-side — they will NOT appear in
683
+ * `variables`. Callers should warn the user about `decryptionFailures`
684
+ * so they know those secrets weren't injected.
677
685
  */
678
686
  async listVariables(projectId, environment, organizationId) {
679
687
  const params = { projectId };
@@ -683,11 +691,29 @@ var APIClient = class {
683
691
  if (organizationId) {
684
692
  params.organizationId = organizationId;
685
693
  }
694
+ const response = await this.get("/api/cli/variables", params);
695
+ return {
696
+ variables: response.data || [],
697
+ decryptionFailures: response.meta?.decryptionFailures ?? []
698
+ };
699
+ }
700
+ /**
701
+ * Check the variable fingerprint for a project/environment.
702
+ *
703
+ * Returns a short hash of variable metadata (id + version + updatedAt)
704
+ * WITHOUT decrypting vault secrets. The CLI uses this to decide whether
705
+ * a cached variable set is still current before doing a full (expensive)
706
+ * fetch. If the fingerprint matches, the cache can be extended for free.
707
+ */
708
+ async checkFingerprint(projectId, environment, organizationId) {
709
+ const params = { projectId };
710
+ if (environment) params.environment = environment;
711
+ if (organizationId) params.organizationId = organizationId;
686
712
  const response = await this.get(
687
- "/api/cli/variables",
713
+ "/api/cli/variables/fingerprint",
688
714
  params
689
715
  );
690
- return response.data || [];
716
+ return response.fingerprint;
691
717
  }
692
718
  /**
693
719
  * Get a variable by ID (with decrypted value)
@@ -904,6 +930,8 @@ var variableSchema = z.object({
904
930
  description: z.string().optional(),
905
931
  isSensitive: z.boolean().optional(),
906
932
  version: z.number().optional(),
933
+ updatedAt: z.number().optional(),
934
+ createdAt: z.number().optional(),
907
935
  tags: z.array(variableTagSchema).optional()
908
936
  });
909
937
  var environmentSchema = z.enum([
@@ -2264,11 +2292,11 @@ var pushCommand = new Command4("push").description("Upload local .env file to cl
2264
2292
  if (fmt === "env") {
2265
2293
  localVars = readEnvFile(inputPath);
2266
2294
  } else {
2267
- const { readFileSync: readFileSync4, existsSync: existsSync4 } = await import("fs");
2268
- if (!existsSync4(inputPath)) {
2295
+ const { readFileSync: readFileSync5, existsSync: existsSync5 } = await import("fs");
2296
+ if (!existsSync5(inputPath)) {
2269
2297
  localVars = null;
2270
2298
  } else {
2271
- const content = readFileSync4(inputPath, "utf-8");
2299
+ const content = readFileSync5(inputPath, "utf-8");
2272
2300
  localVars = parse(content, fmt, { prefix: options.prefix });
2273
2301
  }
2274
2302
  }
@@ -3712,40 +3740,417 @@ var whoamiCommand = new Command12("whoami").description("Show the current authen
3712
3740
  }
3713
3741
  });
3714
3742
 
3715
- // src/commands/man.ts
3743
+ // src/commands/run.ts
3716
3744
  import { Command as Command13 } from "commander";
3745
+ import { spawn as spawn2 } from "child_process";
3717
3746
  import chalk15 from "chalk";
3747
+
3748
+ // src/lib/variables-cache.ts
3749
+ import { createHash } from "crypto";
3750
+ import {
3751
+ readFileSync as readFileSync4,
3752
+ writeFileSync as writeFileSync4,
3753
+ mkdirSync as mkdirSync2,
3754
+ existsSync as existsSync4,
3755
+ readdirSync,
3756
+ unlinkSync as unlinkSync3,
3757
+ statSync as statSync2
3758
+ } from "fs";
3759
+ import { join as join4, dirname as dirname2 } from "path";
3760
+ function getCacheDir() {
3761
+ return join4(dirname2(getConfigPath()), "run-cache");
3762
+ }
3763
+ function getCacheKey(projectId, environment, organizationId) {
3764
+ const tokenSlice = (getAccessToken() ?? "").slice(0, 16);
3765
+ return createHash("sha256").update(`${projectId}:${environment}:${organizationId}:${tokenSlice}`).digest("hex").slice(0, 20);
3766
+ }
3767
+ function getCachePath(key) {
3768
+ return join4(getCacheDir(), `${key}.json`);
3769
+ }
3770
+ function computeFingerprint(variables) {
3771
+ return createHash("sha256").update(
3772
+ variables.map((v) => `${v._id}:${v.version ?? 0}:${v.updatedAt ?? 0}`).sort().join("|")
3773
+ ).digest("hex").slice(0, 16);
3774
+ }
3775
+ function readCache(projectId, environment, organizationId) {
3776
+ try {
3777
+ const key = getCacheKey(projectId, environment, organizationId);
3778
+ const path = getCachePath(key);
3779
+ if (!existsSync4(path)) return null;
3780
+ const raw = readFileSync4(path, "utf-8");
3781
+ const entry = JSON.parse(raw);
3782
+ if (entry.apiUrl !== getApiUrl()) return null;
3783
+ return entry;
3784
+ } catch {
3785
+ return null;
3786
+ }
3787
+ }
3788
+ function probeCache(projectId, environment, organizationId, ttlSeconds) {
3789
+ const entry = readCache(projectId, environment, organizationId);
3790
+ if (!entry) return { hit: false };
3791
+ const fresh = isFresh(entry, ttlSeconds);
3792
+ return { hit: true, fresh, entry };
3793
+ }
3794
+ function writeCache(projectId, environment, organizationId, variables) {
3795
+ try {
3796
+ const cacheDir = getCacheDir();
3797
+ mkdirSync2(cacheDir, { recursive: true, mode: 448 });
3798
+ const key = getCacheKey(projectId, environment, organizationId);
3799
+ const path = getCachePath(key);
3800
+ const entry = {
3801
+ variables,
3802
+ fetchedAt: Date.now(),
3803
+ projectId,
3804
+ environment,
3805
+ organizationId,
3806
+ apiUrl: getApiUrl(),
3807
+ fingerprint: computeFingerprint(variables)
3808
+ };
3809
+ writeFileSync4(path, JSON.stringify(entry, null, 2), {
3810
+ encoding: "utf-8",
3811
+ mode: 384
3812
+ // owner read/write only — same as .env
3813
+ });
3814
+ } catch {
3815
+ }
3816
+ }
3817
+ function extendCacheFreshness(projectId, environment, organizationId) {
3818
+ try {
3819
+ const key = getCacheKey(projectId, environment, organizationId);
3820
+ const path = getCachePath(key);
3821
+ if (!existsSync4(path)) return;
3822
+ const raw = readFileSync4(path, "utf-8");
3823
+ const entry = JSON.parse(raw);
3824
+ entry.fetchedAt = Date.now();
3825
+ writeFileSync4(path, JSON.stringify(entry, null, 2), {
3826
+ encoding: "utf-8",
3827
+ mode: 384
3828
+ });
3829
+ } catch {
3830
+ }
3831
+ }
3832
+ function isFresh(entry, ttlSeconds) {
3833
+ return Date.now() - entry.fetchedAt < ttlSeconds * 1e3;
3834
+ }
3835
+ function formatAge(fetchedAt) {
3836
+ const ageSec = Math.floor((Date.now() - fetchedAt) / 1e3);
3837
+ if (ageSec < 60) return `${ageSec}s ago`;
3838
+ const ageMin = Math.floor(ageSec / 60);
3839
+ if (ageMin < 60) return `${ageMin}m ago`;
3840
+ return `${Math.floor(ageMin / 60)}h ago`;
3841
+ }
3842
+
3843
+ // src/commands/run.ts
3844
+ var DEFAULT_TTL = 3600;
3845
+ var runCommand = new Command13("run").description(
3846
+ "Run a command with project secrets injected as environment variables (no .env file written)"
3847
+ ).argument("[command...]", "Command and arguments to execute").option(
3848
+ "-e, --env <environment>",
3849
+ "Environment to load (development, staging, production)"
3850
+ ).option(
3851
+ "-p, --project <name-or-id>",
3852
+ "Linked project to load secrets from (defaults to active)"
3853
+ ).option(
3854
+ "-o, --organization <id>",
3855
+ "Organization id (overrides linked project's org)"
3856
+ ).option(
3857
+ "--keep-existing",
3858
+ "Don't overwrite env vars that are already set in the parent shell"
3859
+ ).option(
3860
+ "--print",
3861
+ "Print the variables that would be injected and exit (no command runs)"
3862
+ ).option(
3863
+ "--shell",
3864
+ "Run the command through the user's shell (enables pipes, &&, $VAR expansion)"
3865
+ ).option("-q, --quiet", "Suppress informational messages").option("--no-cache", "Skip cache and always fetch fresh secrets from server").option(
3866
+ "--cache-ttl <seconds>",
3867
+ "How long cached secrets stay fresh before a fingerprint check (default: 3600s / 1h)",
3868
+ String(DEFAULT_TTL)
3869
+ ).passThroughOptions().allowExcessArguments().action(async (commandArgs, options) => {
3870
+ try {
3871
+ if (!options.print && (!commandArgs || commandArgs.length === 0)) {
3872
+ throw invalidInput(
3873
+ "No command provided. Usage: envpilot run -- <command> [args...]"
3874
+ );
3875
+ }
3876
+ if (!isAuthenticated()) {
3877
+ throw notAuthenticated();
3878
+ }
3879
+ const config2 = readProjectConfigV2();
3880
+ if (!config2) throw notInitialized();
3881
+ const project = options.project ? resolveProject(config2, options.project) : getActiveProject(config2);
3882
+ if (!project) {
3883
+ if (options.project) {
3884
+ error(`Project not found in this directory: ${options.project}`);
3885
+ console.log();
3886
+ console.log("Linked projects:");
3887
+ for (const p of config2.projects) {
3888
+ console.log(` ${p.projectName || p.projectId} (${p.environment})`);
3889
+ }
3890
+ process.exit(1);
3891
+ }
3892
+ throw notInitialized();
3893
+ }
3894
+ const environment = options.env || project.environment;
3895
+ const organizationId = options.organization || project.organizationId;
3896
+ const ttl = Math.max(
3897
+ 1,
3898
+ parseInt(options.cacheTtl ?? String(DEFAULT_TTL), 10) || DEFAULT_TTL
3899
+ );
3900
+ let variables;
3901
+ let cacheHit = false;
3902
+ let cacheAge = "";
3903
+ if (options.cache === false) {
3904
+ variables = await doFetch(
3905
+ project,
3906
+ environment,
3907
+ organizationId,
3908
+ options.quiet
3909
+ );
3910
+ writeCache(project.projectId, environment, organizationId, variables);
3911
+ } else {
3912
+ const probe = probeCache(
3913
+ project.projectId,
3914
+ environment,
3915
+ organizationId,
3916
+ ttl
3917
+ );
3918
+ if (probe.hit && probe.fresh) {
3919
+ variables = probe.entry.variables;
3920
+ cacheHit = true;
3921
+ cacheAge = formatAge(probe.entry.fetchedAt);
3922
+ } else if (probe.hit && !probe.fresh) {
3923
+ try {
3924
+ const api = createAPIClient();
3925
+ const serverFingerprint = await api.checkFingerprint(
3926
+ project.projectId,
3927
+ environment,
3928
+ organizationId
3929
+ );
3930
+ if (serverFingerprint === probe.entry.fingerprint) {
3931
+ extendCacheFreshness(
3932
+ project.projectId,
3933
+ environment,
3934
+ organizationId
3935
+ );
3936
+ variables = probe.entry.variables;
3937
+ cacheHit = true;
3938
+ cacheAge = formatAge(probe.entry.fetchedAt);
3939
+ } else {
3940
+ variables = await doFetch(
3941
+ project,
3942
+ environment,
3943
+ organizationId,
3944
+ options.quiet,
3945
+ "Secrets updated, refreshing"
3946
+ );
3947
+ writeCache(
3948
+ project.projectId,
3949
+ environment,
3950
+ organizationId,
3951
+ variables
3952
+ );
3953
+ }
3954
+ } catch {
3955
+ variables = probe.entry.variables;
3956
+ cacheHit = true;
3957
+ cacheAge = formatAge(probe.entry.fetchedAt);
3958
+ }
3959
+ } else {
3960
+ variables = await doFetch(
3961
+ project,
3962
+ environment,
3963
+ organizationId,
3964
+ options.quiet
3965
+ );
3966
+ writeCache(project.projectId, environment, organizationId, variables);
3967
+ }
3968
+ }
3969
+ if (variables.length === 0) {
3970
+ warning(
3971
+ `No variables found for ${environment}. Running command without injected secrets.`
3972
+ );
3973
+ }
3974
+ const secrets = {};
3975
+ for (const v of variables) {
3976
+ secrets[v.key] = v.value;
3977
+ }
3978
+ if (options.print) {
3979
+ printInjectionPreview(secrets, project, environment);
3980
+ return;
3981
+ }
3982
+ const baseEnv = { ...process.env };
3983
+ const finalEnv = options.keepExisting ? { ...secrets, ...baseEnv } : { ...baseEnv, ...secrets };
3984
+ const overridden = [];
3985
+ if (!options.keepExisting) {
3986
+ for (const key of Object.keys(secrets)) {
3987
+ if (process.env[key] !== void 0 && process.env[key] !== secrets[key]) {
3988
+ overridden.push(key);
3989
+ }
3990
+ }
3991
+ }
3992
+ if (!options.quiet) {
3993
+ const injectedCount = Object.keys(secrets).length;
3994
+ const cacheTag = cacheHit ? chalk15.dim(` \u26A1 cache (${cacheAge})`) : "";
3995
+ info(
3996
+ `Injected ${chalk15.bold(injectedCount)} ${injectedCount === 1 ? "variable" : "variables"} from ${chalk15.bold(`${project.projectName || project.projectId}/${environment}`)}${cacheTag}`
3997
+ );
3998
+ if (overridden.length > 0) {
3999
+ warning(
4000
+ `Overriding ${overridden.length} shell var${overridden.length === 1 ? "" : "s"}: ${overridden.slice(0, 3).join(", ")}${overridden.length > 3 ? `, +${overridden.length - 3} more` : ""}`
4001
+ );
4002
+ info("Use --keep-existing to keep your shell values instead.");
4003
+ }
4004
+ console.log();
4005
+ }
4006
+ await runChild(commandArgs, finalEnv, options);
4007
+ } catch (err) {
4008
+ await handleError(err);
4009
+ }
4010
+ });
4011
+ async function doFetch(project, environment, organizationId, quiet, labelPrefix = "Loading") {
4012
+ const label = `${labelPrefix} ${chalk15.bold(environment)} secrets for ${chalk15.bold(project.projectName || project.projectId)}...`;
4013
+ const api = createAPIClient();
4014
+ const { variables, decryptionFailures } = quiet ? await api.listVariables(project.projectId, environment, organizationId) : await withSpinner(
4015
+ label,
4016
+ () => api.listVariables(project.projectId, environment, organizationId)
4017
+ );
4018
+ if (decryptionFailures.length > 0) {
4019
+ for (const key of decryptionFailures) {
4020
+ warning(
4021
+ `Could not decrypt ${chalk15.bold(key)} \u2014 skipped (vault error, check server logs)`
4022
+ );
4023
+ }
4024
+ }
4025
+ return variables;
4026
+ }
4027
+ function printInjectionPreview(secrets, project, environment) {
4028
+ const keys = Object.keys(secrets).sort();
4029
+ console.log();
4030
+ console.log(
4031
+ chalk15.bold(
4032
+ `Would inject ${keys.length} ${keys.length === 1 ? "variable" : "variables"} from ${chalk15.cyan(`${project.projectName || project.projectId}/${environment}`)}:`
4033
+ )
4034
+ );
4035
+ console.log();
4036
+ if (keys.length === 0) {
4037
+ console.log(chalk15.dim(" (no variables)"));
4038
+ } else {
4039
+ for (const key of keys) {
4040
+ const value = secrets[key];
4041
+ const masked = maskForPreview(value);
4042
+ console.log(` ${chalk15.cyan(key)}=${chalk15.dim(masked)}`);
4043
+ }
4044
+ }
4045
+ console.log();
4046
+ success("Dry run \u2014 no command executed.");
4047
+ }
4048
+ function maskForPreview(value) {
4049
+ if (value.length <= 6) return "******";
4050
+ return `${value.slice(0, 4)}\u2026${value.slice(-2)} (${value.length} chars)`;
4051
+ }
4052
+ function runChild(commandArgs, env, options) {
4053
+ return new Promise((resolve3) => {
4054
+ const [command, ...args] = commandArgs;
4055
+ if (!command) {
4056
+ process.exit(1);
4057
+ }
4058
+ const isWindows = process.platform === "win32";
4059
+ const useShell = options.shell === true || isWindows;
4060
+ const child = spawn2(command, args, {
4061
+ stdio: "inherit",
4062
+ env,
4063
+ shell: useShell
4064
+ });
4065
+ const signals = [
4066
+ "SIGINT",
4067
+ "SIGTERM",
4068
+ "SIGHUP",
4069
+ "SIGQUIT"
4070
+ ];
4071
+ const forward = {};
4072
+ for (const sig of signals) {
4073
+ const handler = () => {
4074
+ if (!child.killed) {
4075
+ try {
4076
+ child.kill(sig);
4077
+ } catch {
4078
+ }
4079
+ }
4080
+ };
4081
+ forward[sig] = handler;
4082
+ process.on(sig, handler);
4083
+ }
4084
+ const cleanup = () => {
4085
+ for (const sig of signals) {
4086
+ const handler = forward[sig];
4087
+ if (handler) process.off(sig, handler);
4088
+ }
4089
+ };
4090
+ child.on("error", (err) => {
4091
+ cleanup();
4092
+ const code = err.code;
4093
+ if (code === "ENOENT") {
4094
+ error(
4095
+ `Command not found: ${command}. Make sure it is installed and on your PATH.`
4096
+ );
4097
+ } else if (code === "EACCES") {
4098
+ error(`Permission denied executing: ${command}`);
4099
+ } else {
4100
+ error(`Failed to spawn process: ${err.message}`);
4101
+ }
4102
+ process.exit(1);
4103
+ });
4104
+ child.on("exit", (code, signal) => {
4105
+ cleanup();
4106
+ if (signal) {
4107
+ try {
4108
+ process.kill(process.pid, signal);
4109
+ } catch {
4110
+ process.exit(1);
4111
+ }
4112
+ } else {
4113
+ process.exit(code ?? 0);
4114
+ }
4115
+ resolve3();
4116
+ });
4117
+ });
4118
+ }
4119
+
4120
+ // src/commands/man.ts
4121
+ import { Command as Command14 } from "commander";
4122
+ import chalk16 from "chalk";
3718
4123
  function printCommandManual(commandName) {
3719
4124
  const command = findCommandDefinition(commandName);
3720
4125
  if (!command) {
3721
- console.log(chalk15.red(`Unknown command reference: ${commandName}`));
4126
+ console.log(chalk16.red(`Unknown command reference: ${commandName}`));
3722
4127
  console.log();
3723
4128
  console.log("Run `envpilot man` to see all supported commands.");
3724
4129
  process.exit(1);
3725
4130
  }
3726
- console.log(chalk15.bold(formatArgv(command.argv)));
4131
+ console.log(chalk16.bold(formatArgv(command.argv)));
3727
4132
  if (command.args) {
3728
- console.log(chalk15.dim(command.args));
4133
+ console.log(chalk16.dim(command.args));
3729
4134
  }
3730
4135
  blank();
3731
4136
  console.log(command.description);
3732
4137
  blank();
3733
- console.log(chalk15.green("Examples"));
4138
+ console.log(chalk16.green("Examples"));
3734
4139
  for (const example of command.examples) {
3735
- console.log(` ${chalk15.cyan(formatArgv(example))}`);
4140
+ console.log(` ${chalk16.cyan(formatArgv(example))}`);
3736
4141
  }
3737
4142
  blank();
3738
- console.log(chalk15.green("Notes"));
4143
+ console.log(chalk16.green("Notes"));
3739
4144
  for (const note of command.notes) {
3740
4145
  console.log(` - ${note}`);
3741
4146
  }
3742
4147
  }
3743
4148
  function printManualIndex(commands) {
3744
- console.log(chalk15.bold("ENVPILOT(1)"));
4149
+ console.log(chalk16.bold("ENVPILOT(1)"));
3745
4150
  console.log("TypeScript CLI manual");
3746
4151
  blank();
3747
4152
  console.log(
3748
- `Total supported top-level commands: ${chalk15.green(String(CLI_COMMAND_COUNT))}`
4153
+ `Total supported top-level commands: ${chalk16.green(String(CLI_COMMAND_COUNT))}`
3749
4154
  );
3750
4155
  blank();
3751
4156
  console.log(
@@ -3753,15 +4158,15 @@ function printManualIndex(commands) {
3753
4158
  );
3754
4159
  blank();
3755
4160
  line();
3756
- console.log(chalk15.green("Commands"));
4161
+ console.log(chalk16.green("Commands"));
3757
4162
  for (const command of commands) {
3758
4163
  console.log(
3759
- ` ${chalk15.cyan(formatArgv(command.argv).padEnd(24))} ${chalk15.dim(command.description)}`
4164
+ ` ${chalk16.cyan(formatArgv(command.argv).padEnd(24))} ${chalk16.dim(command.description)}`
3760
4165
  );
3761
4166
  }
3762
4167
  blank();
3763
4168
  line();
3764
- console.log(chalk15.green("Security"));
4169
+ console.log(chalk16.green("Security"));
3765
4170
  console.log(" - `.env*` is ignored by the repository `.gitignore`.");
3766
4171
  console.log(
3767
4172
  " - `envpilot init` and `envpilot sync` ensure env files are added to `.gitignore` locally."
@@ -3774,7 +4179,7 @@ function printManualIndex(commands) {
3774
4179
  );
3775
4180
  blank();
3776
4181
  line();
3777
- console.log(chalk15.green("Usage"));
4182
+ console.log(chalk16.green("Usage"));
3778
4183
  console.log(
3779
4184
  " - `envpilot` or `envpilot ui` opens the interactive terminal UI."
3780
4185
  );
@@ -3783,7 +4188,7 @@ function printManualIndex(commands) {
3783
4188
  );
3784
4189
  }
3785
4190
  function createManCommand(commands) {
3786
- return new Command13("man").description("Show the CLI manual page").argument("[command]", "Optional command to show detailed manual for").action((command) => {
4191
+ return new Command14("man").description("Show the CLI manual page").argument("[command]", "Optional command to show detailed manual for").action((command) => {
3787
4192
  if (command) {
3788
4193
  printCommandManual(command);
3789
4194
  return;
@@ -3793,9 +4198,9 @@ function createManCommand(commands) {
3793
4198
  }
3794
4199
 
3795
4200
  // src/commands/ui.ts
3796
- import { Command as Command14 } from "commander";
4201
+ import { Command as Command15 } from "commander";
3797
4202
  function createUICommand() {
3798
- return new Command14("ui").alias("dashboard").description("Open the interactive Ink-powered terminal UI").action(async () => {
4203
+ return new Command15("ui").alias("dashboard").description("Open the interactive Ink-powered terminal UI").action(async () => {
3799
4204
  if (process.env.ENVPILOT_TUI_CHILD === "1") {
3800
4205
  return;
3801
4206
  }
@@ -3945,6 +4350,42 @@ var COMMAND_CATALOG = [
3945
4350
  topLevel: true,
3946
4351
  createCommand: () => pushCommand
3947
4352
  },
4353
+ {
4354
+ id: "run",
4355
+ title: "Run command with secrets",
4356
+ category: "Sync",
4357
+ description: "Inject project secrets into a child process without writing a .env file (envpilot run -- bun dev).",
4358
+ argv: ["run"],
4359
+ args: "[--env <environment>] [--project <name-or-id>] [--keep-existing] [--print] -- <command> [args...]",
4360
+ examples: [
4361
+ ["run", "--", "bun", "dev"],
4362
+ ["run", "--env", "production", "--", "node", "server.js"],
4363
+ ["run", "--project", "api", "--", "pnpm", "test"],
4364
+ ["run", "--print"],
4365
+ ["run", "--keep-existing", "--", "bun", "dev"]
4366
+ ],
4367
+ websiteSurface: "Maps to `/api/cli/variables` for read access.",
4368
+ notes: [
4369
+ "Use `--` to separate envpilot flags from the command to execute.",
4370
+ "Secrets override existing shell vars by default; pass --keep-existing to flip.",
4371
+ "On Windows, the command is run through the shell so .cmd / .bat files resolve.",
4372
+ "Signals (SIGINT, SIGTERM, SIGHUP, SIGQUIT) are forwarded to the child process.",
4373
+ "Use --print to inspect what would be injected without executing anything."
4374
+ ],
4375
+ keywords: [
4376
+ "run",
4377
+ "exec",
4378
+ "spawn",
4379
+ "inject",
4380
+ "secrets",
4381
+ "env",
4382
+ "ephemeral",
4383
+ "no-file",
4384
+ "doppler"
4385
+ ],
4386
+ topLevel: true,
4387
+ createCommand: () => runCommand
4388
+ },
3948
4389
  {
3949
4390
  id: "list",
3950
4391
  title: "List resources",
package/dist/index.js CHANGED
@@ -4,18 +4,18 @@ import {
4
4
  getTopLevelCommandCatalog,
5
5
  initSentry,
6
6
  openTUI
7
- } from "./chunk-CIIQSYAS.js";
7
+ } from "./chunk-RUYGAIH5.js";
8
8
 
9
9
  // src/lib/program.ts
10
10
  import { Command } from "commander";
11
11
 
12
12
  // src/lib/cli-version.ts
13
- var CLI_VERSION = true ? "1.5.0" : "0.0.0";
13
+ var CLI_VERSION = true ? "1.6.0" : "0.0.0";
14
14
 
15
15
  // src/lib/version-check.ts
16
16
  import chalk from "chalk";
17
17
  import Conf from "conf";
18
- var CLI_VERSION2 = true ? "1.5.0" : "0.0.0";
18
+ var CLI_VERSION2 = true ? "1.6.0" : "0.0.0";
19
19
  var CHECK_INTERVAL = 60 * 60 * 1e3;
20
20
  var _cache = null;
21
21
  function getCache() {
@@ -60,7 +60,7 @@ function checkForUpdate() {
60
60
  // src/lib/program.ts
61
61
  function createProgram() {
62
62
  const program = new Command();
63
- program.name("envpilot").description("Envpilot CLI - Sync, secure, and share environment variables").version(CLI_VERSION);
63
+ program.name("envpilot").description("Envpilot CLI - Sync, secure, and share environment variables").version(CLI_VERSION).enablePositionalOptions();
64
64
  for (const command of getTopLevelCommandCatalog()) {
65
65
  if (command.createCommand) {
66
66
  program.addCommand(command.createCommand());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envpilot/cli",
3
- "version": "1.5.0",
3
+ "version": "1.6.0",
4
4
  "description": "Envpilot CLI — sync and manage environment variables from the terminal",
5
5
  "type": "module",
6
6
  "bin": {