@envpilot/cli 1.9.1 → 1.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -64,6 +64,26 @@ The TUI returns after each command finishes, so you can run multiple commands in
64
64
  | `envpilot pull [options]` | Pull variables into a local `.env` file |
65
65
  | `envpilot push [options]` | Push local `.env` changes to Envpilot |
66
66
 
67
+ ### Variable Requests
68
+
69
+ Developers don't have direct write access — instead they submit a request,
70
+ and an owner, project manager, or team lead reviews it on the dashboard
71
+ (choosing the final environments on approval).
72
+
73
+ | Command | Description |
74
+ | ----------------------------- | ---------------------------------------------------------------------- |
75
+ | `envpilot request [options]` | Request a new variable (interactive: key, value, environments) |
76
+ | `envpilot requests [options]` | List variable requests for the linked project with their review status |
77
+
78
+ ```bash
79
+ envpilot request # guided prompt: key → value → description → environments
80
+ envpilot requests # all requests for the linked project
81
+ envpilot requests --status pending # filter: pending | approved | rejected | canceled
82
+ ```
83
+
84
+ Environment choices are limited to the environments you have access to —
85
+ a developer scoped to `development` cannot request a production variable.
86
+
67
87
  ### Browsing Resources
68
88
 
69
89
  | Command | Description |
@@ -107,6 +127,8 @@ Run `envpilot man` for the full command reference, or `envpilot man <command>` f
107
127
  | `--dry-run` | `pull` | Preview changes without writing to disk |
108
128
  | `--json` | `list projects`, `list organizations`, `usage` | Output as JSON |
109
129
  | `--force` | `unlink` | Skip confirmation prompt |
130
+ | `--project <id>` | `request`, `requests` | Target a specific linked project |
131
+ | `--status <status>` | `requests` | Filter requests: pending, approved, rejected, canceled |
110
132
 
111
133
  ## Pull Formats
112
134
 
@@ -124,17 +146,20 @@ envpilot pull --format docker-compose # Docker Compose
124
146
 
125
147
  ## Role-Based Access
126
148
 
127
- Envpilot enforces two-tier role-based access control:
149
+ Envpilot uses one unified organization role per user:
128
150
 
129
- **Organization roles** -- Admin, Team Lead, Member
151
+ **Owner > Project Manager > Team Lead > Developer**
130
152
 
131
- **Project roles** -- Manager, Developer, Viewer
153
+ What you can do in a project follows from this role plus whether you are
154
+ assigned to the project. Developers can additionally be scoped to specific
155
+ environments, and per-variable read/write grants control their access.
132
156
 
133
- | Role | Pull | Push (direct) | Push (approval request) | Manage permissions |
134
- | --------- | ------------------------ | ------------- | ----------------------- | ------------------ |
135
- | Manager | Yes | Yes | -- | Yes |
136
- | Developer | Yes | No | Yes | No |
137
- | Viewer | Permitted variables only | No | No | No |
157
+ | Role | Pull | Push (direct) | Request new variable | Review requests | Manage permissions |
158
+ | --------------- | -------------------------------------- | ------------- | -------------------- | --------------- | ------------------ |
159
+ | Owner | Yes | Yes | -- (creates direct) | Yes | Yes |
160
+ | Project Manager | Yes (assigned projects) | Yes | -- (creates direct) | Yes | Yes |
161
+ | Team Lead | Yes (assigned projects) | Yes | -- (creates direct) | Yes | Yes |
162
+ | Developer | Granted variables, scoped environments | No | Yes | No | No |
138
163
 
139
164
  ## Security
140
165
 
@@ -4,7 +4,7 @@ import {
4
4
  getTopLevelCommandCatalog,
5
5
  getUser,
6
6
  isAuthenticated
7
- } from "./chunk-SQRQ5VWJ.js";
7
+ } from "./chunk-QYRSBLFW.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.9.1" : "0.0.0",
10
+ release: true ? "1.10.0" : "0.0.0",
11
11
  // Free tier: disable performance monitoring
12
12
  tracesSampleRate: 0,
13
13
  beforeSend(event) {
@@ -469,7 +469,7 @@ import { jsx } from "react/jsx-runtime";
469
469
  async function openTUI() {
470
470
  const [{ render }, { CLIApp }, { PressAnyKey }] = await Promise.all([
471
471
  import("ink"),
472
- import("./app-3LJYJ4RK.js"),
472
+ import("./app-UT26PMDR.js"),
473
473
  import("./press-any-key-64XFP4O2.js")
474
474
  ]);
475
475
  while (true) {
@@ -1169,6 +1169,29 @@ var APIClient = class {
1169
1169
  async bulkUpsertVariables(data) {
1170
1170
  return this.post("/api/cli/variables/bulk", data);
1171
1171
  }
1172
+ /**
1173
+ * Submit a variable request (developers only — owners/PMs/team leads
1174
+ * create variables directly and get a 403 from the server here).
1175
+ */
1176
+ async createVariableRequest(data) {
1177
+ const response = await this.post(
1178
+ "/api/cli/variable-requests",
1179
+ data
1180
+ );
1181
+ if (!response.data) {
1182
+ throw new APIError("No variable request returned by server", 500);
1183
+ }
1184
+ return response.data.request;
1185
+ }
1186
+ /**
1187
+ * List variable requests for a project, optionally filtered by status.
1188
+ */
1189
+ async listVariableRequests(projectId, status) {
1190
+ const params = { projectId };
1191
+ if (status) params.status = status;
1192
+ const response = await this.get("/api/cli/variable-requests", params);
1193
+ return response.data?.requests ?? [];
1194
+ }
1172
1195
  // ============================================
1173
1196
  // Authentication methods
1174
1197
  // ============================================
@@ -1457,6 +1480,32 @@ var projectConfigV2Schema = z.object({
1457
1480
  activeProjectId: z.string(),
1458
1481
  projects: z.array(projectEntrySchema).min(1)
1459
1482
  });
1483
+ var variableRequestStatusSchema = z.enum([
1484
+ "pending",
1485
+ "approved",
1486
+ "rejected",
1487
+ "canceled"
1488
+ ]);
1489
+ var variableRequestUserSchema = z.object({
1490
+ _id: z.string(),
1491
+ email: z.string().optional(),
1492
+ name: z.string().optional()
1493
+ }).nullable().optional();
1494
+ var variableRequestSchema = z.object({
1495
+ _id: z.string(),
1496
+ key: z.string(),
1497
+ description: z.string().optional(),
1498
+ environments: z.array(z.string()),
1499
+ projectId: z.string(),
1500
+ organizationId: z.string().optional(),
1501
+ isSensitive: z.boolean().optional(),
1502
+ status: variableRequestStatusSchema,
1503
+ reviewReason: z.string().optional(),
1504
+ createdAt: z.number(),
1505
+ updatedAt: z.number().optional(),
1506
+ requester: variableRequestUserSchema,
1507
+ reviewer: variableRequestUserSchema
1508
+ });
1460
1509
 
1461
1510
  // src/lib/project-config.ts
1462
1511
  var CONFIG_FILE_NAME = ".envpilot";
@@ -3757,8 +3806,8 @@ async function handlePath() {
3757
3806
  ]);
3758
3807
  }
3759
3808
  async function handleReset() {
3760
- const inquirer6 = await import("inquirer");
3761
- const { confirm } = await inquirer6.default.prompt([
3809
+ const inquirer7 = await import("inquirer");
3810
+ const { confirm } = await inquirer7.default.prompt([
3762
3811
  {
3763
3812
  type: "confirm",
3764
3813
  name: "confirm",
@@ -4895,6 +4944,443 @@ function createUICommand() {
4895
4944
  });
4896
4945
  }
4897
4946
 
4947
+ // src/commands/request.ts
4948
+ import { Command as Command17 } from "commander";
4949
+ import chalk17 from "chalk";
4950
+ import inquirer6 from "inquirer";
4951
+
4952
+ // src/lib/variable-requests.ts
4953
+ import { z as z3 } from "zod";
4954
+ var ALL_REQUEST_ENVIRONMENTS = [
4955
+ "development",
4956
+ "staging",
4957
+ "production"
4958
+ ];
4959
+ var requestKeySchema = z3.string().min(1, "Key is required").max(100, "Key must be 100 characters or less").regex(
4960
+ /^[A-Z][A-Z0-9_]*$/,
4961
+ "Key must be uppercase, start with a letter, and contain only letters, numbers, and underscores"
4962
+ );
4963
+ var requestValueSchema = z3.string().min(1, "Value is required");
4964
+ var requestDescriptionSchema = z3.string().max(500, "Description must be 500 characters or less");
4965
+ function validateRequestKey(key) {
4966
+ const result = requestKeySchema.safeParse(key);
4967
+ if (result.success) return { valid: true };
4968
+ return {
4969
+ valid: false,
4970
+ error: result.error.issues[0]?.message ?? "Invalid key"
4971
+ };
4972
+ }
4973
+ function validateRequestValue(value) {
4974
+ const result = requestValueSchema.safeParse(value);
4975
+ if (result.success) return { valid: true };
4976
+ return {
4977
+ valid: false,
4978
+ error: result.error.issues[0]?.message ?? "Invalid value"
4979
+ };
4980
+ }
4981
+ function validateRequestDescription(description) {
4982
+ if (description.length === 0) return { valid: true };
4983
+ const result = requestDescriptionSchema.safeParse(description);
4984
+ if (result.success) return { valid: true };
4985
+ return {
4986
+ valid: false,
4987
+ error: result.error.issues[0]?.message ?? "Invalid description"
4988
+ };
4989
+ }
4990
+ function allowedRequestEnvironments(environmentScope) {
4991
+ if (environmentScope == null) {
4992
+ return [...ALL_REQUEST_ENVIRONMENTS];
4993
+ }
4994
+ return ALL_REQUEST_ENVIRONMENTS.filter(
4995
+ (env) => environmentScope.includes(env)
4996
+ );
4997
+ }
4998
+ function buildCreateVariableRequestBody(input) {
4999
+ const description = input.description?.trim();
5000
+ return {
5001
+ projectId: input.projectId,
5002
+ key: input.key.trim(),
5003
+ value: input.value,
5004
+ ...description ? { description } : {},
5005
+ environments: input.environments,
5006
+ isSensitive: input.isSensitive ?? false
5007
+ };
5008
+ }
5009
+ function isRequestEligibleProject(project) {
5010
+ const role = normalizeOrgRole(project.unifiedRole ?? project.role);
5011
+ return role === "developer" && project.assigned === true;
5012
+ }
5013
+ function buildEligibleRequestTargets(projects, orgNameById) {
5014
+ return projects.filter(isRequestEligibleProject).map((p) => ({
5015
+ projectId: p._id,
5016
+ projectName: p.name,
5017
+ organizationId: p.organizationId,
5018
+ organizationName: orgNameById[p.organizationId] ?? p.organizationId,
5019
+ environmentScope: p.environmentScope ?? null
5020
+ }));
5021
+ }
5022
+ function formatProjectChoiceLabel(target) {
5023
+ return `${target.projectName} \u2014 ${target.organizationName}`;
5024
+ }
5025
+ function buildProjectChoices(targets) {
5026
+ return targets.map((t) => ({
5027
+ name: formatProjectChoiceLabel(t),
5028
+ value: t.projectId
5029
+ }));
5030
+ }
5031
+ function formatRequestContextBanner(input) {
5032
+ return `Requesting as ${input.email}
5033
+ Project: ${input.projectName} \xB7 Organization: ${input.organizationName}`;
5034
+ }
5035
+ var SUMMARY_LABEL_WIDTH = 15;
5036
+ function formatRequestSummary(input) {
5037
+ const rows = [
5038
+ ["Key:", input.key],
5039
+ [
5040
+ "Project:",
5041
+ formatProjectChoiceLabel({
5042
+ projectName: input.projectName,
5043
+ organizationName: input.organizationName
5044
+ })
5045
+ ],
5046
+ ["Environments:", input.environments.join(", ")],
5047
+ ["Sensitive:", input.isSensitive ? "yes" : "no"]
5048
+ ];
5049
+ return rows.map(([label, value]) => `${label.padEnd(SUMMARY_LABEL_WIDTH)}${value}`).join("\n");
5050
+ }
5051
+ function formatRequestSuccessMessage(input) {
5052
+ return `Request "${input.key}" submitted for ${input.projectName} (${input.organizationName}) \u2014 pending review.`;
5053
+ }
5054
+ function formatRequestsListHeader(input) {
5055
+ return `Requests for ${formatProjectChoiceLabel(input)}`;
5056
+ }
5057
+ function formatRequestRow(request) {
5058
+ return {
5059
+ key: request.key,
5060
+ environments: request.environments.join(", "),
5061
+ status: request.status,
5062
+ requested: new Date(request.createdAt).toLocaleDateString(),
5063
+ reason: request.reviewReason ?? ""
5064
+ };
5065
+ }
5066
+ function formatRequestRows(requests) {
5067
+ return requests.map(formatRequestRow);
5068
+ }
5069
+
5070
+ // src/commands/request.ts
5071
+ var requestCommand = new Command17("request").description(
5072
+ "Request creation of a new environment variable (developers only \u2014 owners, project managers, and team leads create variables directly)"
5073
+ ).option(
5074
+ "--project <name-or-id>",
5075
+ "Submit the request for a specific linked project"
5076
+ ).action(async (options) => {
5077
+ try {
5078
+ if (!isAuthenticated()) {
5079
+ throw notAuthenticated();
5080
+ }
5081
+ const email = getUser()?.email ?? "unknown account";
5082
+ const api = createAPIClient();
5083
+ const projectPassed = Boolean(options.project);
5084
+ const defaultEntry = resolveDefaultEntry(options.project);
5085
+ let target = null;
5086
+ let scope;
5087
+ if (defaultEntry) {
5088
+ const names = await resolveEntryNames(api, defaultEntry);
5089
+ const defaultTarget = {
5090
+ projectId: defaultEntry.projectId,
5091
+ projectName: names.projectName,
5092
+ organizationId: defaultEntry.organizationId,
5093
+ organizationName: names.organizationName
5094
+ };
5095
+ printBanner(email, defaultTarget);
5096
+ let useDefault = projectPassed;
5097
+ if (!projectPassed) {
5098
+ const { proceed } = await inquirer6.prompt([
5099
+ {
5100
+ type: "confirm",
5101
+ name: "proceed",
5102
+ message: "Request a variable for this project?",
5103
+ default: true
5104
+ }
5105
+ ]);
5106
+ useDefault = proceed;
5107
+ }
5108
+ if (useDefault) {
5109
+ const meta = await fetchProjectMeta(api, defaultEntry);
5110
+ const role = normalizeOrgRole(meta?.unifiedRole ?? meta?.role);
5111
+ if (role !== "developer") {
5112
+ warning(
5113
+ "You have direct write access to this project. Use `envpilot push` or create the variable directly instead of submitting a request."
5114
+ );
5115
+ return;
5116
+ }
5117
+ target = defaultTarget;
5118
+ scope = meta?.environmentScope;
5119
+ }
5120
+ }
5121
+ if (!target) {
5122
+ const picked = await pickRequestTarget(api);
5123
+ if (!picked) {
5124
+ info(
5125
+ "You have direct write access in your projects \u2014 create variables from the dashboard."
5126
+ );
5127
+ process.exit(0);
5128
+ }
5129
+ target = picked;
5130
+ scope = picked.environmentScope;
5131
+ printBanner(email, target);
5132
+ }
5133
+ const envChoices = allowedRequestEnvironments(scope);
5134
+ if (envChoices.length === 0) {
5135
+ error(
5136
+ "Your assignment does not include any environments in this project."
5137
+ );
5138
+ process.exit(1);
5139
+ }
5140
+ const answers = await inquirer6.prompt([
5141
+ {
5142
+ type: "input",
5143
+ name: "key",
5144
+ message: "Variable key (e.g. API_SECRET):",
5145
+ validate: (input) => {
5146
+ const result = validateRequestKey(input);
5147
+ return result.valid || result.error;
5148
+ }
5149
+ },
5150
+ {
5151
+ type: "password",
5152
+ name: "value",
5153
+ message: "Variable value:",
5154
+ mask: "*",
5155
+ validate: (input) => {
5156
+ const result = validateRequestValue(input);
5157
+ return result.valid || result.error;
5158
+ }
5159
+ },
5160
+ {
5161
+ type: "input",
5162
+ name: "description",
5163
+ message: "Description (optional):",
5164
+ default: "",
5165
+ validate: (input) => {
5166
+ const result = validateRequestDescription(input);
5167
+ return result.valid || result.error;
5168
+ }
5169
+ },
5170
+ {
5171
+ type: "checkbox",
5172
+ name: "environments",
5173
+ message: "Environments:",
5174
+ choices: envChoices,
5175
+ validate: (input) => input.length > 0 || "Select at least one environment"
5176
+ },
5177
+ {
5178
+ type: "confirm",
5179
+ name: "isSensitive",
5180
+ message: "Is this value sensitive?",
5181
+ default: true
5182
+ }
5183
+ ]);
5184
+ console.log();
5185
+ console.log(
5186
+ formatRequestSummary({
5187
+ key: answers.key.trim(),
5188
+ projectName: target.projectName,
5189
+ organizationName: target.organizationName,
5190
+ environments: answers.environments,
5191
+ isSensitive: answers.isSensitive
5192
+ })
5193
+ );
5194
+ console.log();
5195
+ const { submit } = await inquirer6.prompt([
5196
+ {
5197
+ type: "confirm",
5198
+ name: "submit",
5199
+ message: "Submit this request?",
5200
+ default: true
5201
+ }
5202
+ ]);
5203
+ if (!submit) {
5204
+ info("Request canceled.");
5205
+ return;
5206
+ }
5207
+ const body = buildCreateVariableRequestBody({
5208
+ projectId: target.projectId,
5209
+ key: answers.key,
5210
+ value: answers.value,
5211
+ description: answers.description,
5212
+ environments: answers.environments,
5213
+ isSensitive: answers.isSensitive
5214
+ });
5215
+ const created = await api.createVariableRequest(body);
5216
+ success(
5217
+ formatRequestSuccessMessage({
5218
+ key: created.key,
5219
+ projectName: target.projectName,
5220
+ organizationName: target.organizationName
5221
+ })
5222
+ );
5223
+ info(`Status: ${created.status} \xB7 Id: ${created._id}`);
5224
+ } catch (err) {
5225
+ if (err instanceof APIError && err.statusCode === 403) {
5226
+ error(err.message);
5227
+ return;
5228
+ }
5229
+ await handleError(err);
5230
+ }
5231
+ });
5232
+ function resolveDefaultEntry(projectOption) {
5233
+ const configV2 = readProjectConfigV2();
5234
+ if (!configV2) {
5235
+ if (projectOption) throw notInitialized();
5236
+ return null;
5237
+ }
5238
+ const project = resolveProject(configV2, projectOption);
5239
+ if (!project) {
5240
+ if (projectOption) {
5241
+ error(`Project not found: ${projectOption}`);
5242
+ console.log();
5243
+ console.log("Linked projects:");
5244
+ for (const p of configV2.projects) {
5245
+ console.log(` ${p.projectName || p.projectId} (${p.environment})`);
5246
+ }
5247
+ process.exit(1);
5248
+ }
5249
+ return null;
5250
+ }
5251
+ return project;
5252
+ }
5253
+ function printBanner(email, target) {
5254
+ console.log();
5255
+ console.log(
5256
+ chalk17.dim(
5257
+ formatRequestContextBanner({
5258
+ email,
5259
+ projectName: target.projectName,
5260
+ organizationName: target.organizationName
5261
+ })
5262
+ )
5263
+ );
5264
+ console.log();
5265
+ }
5266
+ async function resolveEntryNames(api, entry) {
5267
+ let projectName = entry.projectName;
5268
+ let organizationName = entry.organizationName;
5269
+ if (!projectName) {
5270
+ try {
5271
+ const project = await api.getProject(entry.projectId);
5272
+ projectName = project.name;
5273
+ } catch {
5274
+ }
5275
+ }
5276
+ if (!organizationName) {
5277
+ try {
5278
+ const orgs = await api.listOrganizations();
5279
+ organizationName = orgs.find((o) => o._id === entry.organizationId)?.name ?? "";
5280
+ } catch {
5281
+ }
5282
+ }
5283
+ return {
5284
+ projectName: projectName || entry.projectId,
5285
+ organizationName: organizationName || entry.organizationId
5286
+ };
5287
+ }
5288
+ async function fetchProjectMeta(api, entry) {
5289
+ const response = await api.get("/api/cli/variables", {
5290
+ projectId: entry.projectId,
5291
+ ...entry.organizationId && { organizationId: entry.organizationId }
5292
+ });
5293
+ return response.meta;
5294
+ }
5295
+ async function pickRequestTarget(api) {
5296
+ const targets = await withSpinner(
5297
+ "Finding projects you can request for...",
5298
+ async () => {
5299
+ const orgs = await api.listOrganizations();
5300
+ const orgNameById = Object.fromEntries(orgs.map((o) => [o._id, o.name]));
5301
+ const perOrg = await Promise.all(
5302
+ orgs.map((org) => api.listProjects(org._id))
5303
+ );
5304
+ return perOrg.flatMap(
5305
+ (projects) => buildEligibleRequestTargets(projects, orgNameById)
5306
+ );
5307
+ }
5308
+ );
5309
+ if (targets.length === 0) return null;
5310
+ const { projectId } = await inquirer6.prompt([
5311
+ {
5312
+ type: "list",
5313
+ name: "projectId",
5314
+ message: "Select a project to request for:",
5315
+ choices: buildProjectChoices(targets)
5316
+ }
5317
+ ]);
5318
+ return targets.find((t) => t.projectId === projectId) ?? null;
5319
+ }
5320
+
5321
+ // src/commands/requests.ts
5322
+ import { Command as Command18 } from "commander";
5323
+ var requestsCommand = new Command18("requests").description("List variable requests for a project").option(
5324
+ "--project <name-or-id>",
5325
+ "List requests for a specific linked project"
5326
+ ).option(
5327
+ "--status <status>",
5328
+ "Filter by status: pending, approved, rejected, canceled"
5329
+ ).action(async (options) => {
5330
+ try {
5331
+ if (!isAuthenticated()) {
5332
+ throw notAuthenticated();
5333
+ }
5334
+ const configV2 = readProjectConfigV2();
5335
+ if (!configV2) throw notInitialized();
5336
+ const project = resolveProject(configV2, options.project);
5337
+ if (!project) {
5338
+ error(`Project not found: ${options.project}`);
5339
+ console.log();
5340
+ console.log("Linked projects:");
5341
+ for (const p of configV2.projects) {
5342
+ console.log(` ${p.projectName || p.projectId} (${p.environment})`);
5343
+ }
5344
+ process.exit(1);
5345
+ }
5346
+ let status;
5347
+ if (options.status) {
5348
+ const parsed = variableRequestStatusSchema.safeParse(options.status);
5349
+ if (!parsed.success) {
5350
+ error(
5351
+ `Invalid status: ${options.status}. Must be one of pending, approved, rejected, canceled.`
5352
+ );
5353
+ process.exit(1);
5354
+ }
5355
+ status = parsed.data;
5356
+ }
5357
+ const api = createAPIClient();
5358
+ const requests = await withSpinner(
5359
+ "Fetching variable requests...",
5360
+ () => api.listVariableRequests(project.projectId, status)
5361
+ );
5362
+ header(
5363
+ formatRequestsListHeader({
5364
+ projectName: project.projectName || project.projectId,
5365
+ organizationName: project.organizationName || project.organizationId
5366
+ })
5367
+ );
5368
+ if (requests.length === 0) {
5369
+ info("No variable requests found.");
5370
+ return;
5371
+ }
5372
+ table(formatRequestRows(requests), [
5373
+ { key: "key", header: "KEY" },
5374
+ { key: "environments", header: "ENVIRONMENTS" },
5375
+ { key: "status", header: "STATUS" },
5376
+ { key: "requested", header: "REQUESTED" },
5377
+ { key: "reason", header: "REASON" }
5378
+ ]);
5379
+ } catch (err) {
5380
+ await handleError(err);
5381
+ }
5382
+ });
5383
+
4898
5384
  // src/lib/command-catalog.ts
4899
5385
  function formatArgv(argv) {
4900
5386
  return ["envpilot", ...argv].join(" ").trim();
@@ -5038,6 +5524,41 @@ var COMMAND_CATALOG = [
5038
5524
  topLevel: true,
5039
5525
  createCommand: () => pushCommand
5040
5526
  },
5527
+ {
5528
+ id: "request",
5529
+ title: "Request a new variable",
5530
+ category: "Sync",
5531
+ description: "Submit a request to create a new environment variable for review (developers only).",
5532
+ argv: ["request"],
5533
+ args: "[--project <name-or-id>]",
5534
+ examples: [["request"], ["request", "--project", "api"]],
5535
+ websiteSurface: "Maps to `/api/cli/variable-requests` (POST).",
5536
+ notes: [
5537
+ "Only assigned developers can submit requests \u2014 owners, project managers, and team leads create variables directly.",
5538
+ "Environment choices are limited to the developer's assigned environment scope.",
5539
+ "An owner, project manager, or team lead must approve the request before the variable is created."
5540
+ ],
5541
+ keywords: ["request", "approval", "developer", "create"],
5542
+ topLevel: true,
5543
+ createCommand: () => requestCommand
5544
+ },
5545
+ {
5546
+ id: "requests",
5547
+ title: "List variable requests",
5548
+ category: "Browse",
5549
+ description: "List pending and past variable requests for a project.",
5550
+ argv: ["requests"],
5551
+ args: "[--project <name-or-id>] [--status <status>]",
5552
+ examples: [["requests"], ["requests", "--status", "pending"]],
5553
+ websiteSurface: "Maps to `/api/cli/variable-requests` (GET).",
5554
+ notes: [
5555
+ "Reviewers (owner, assigned project manager/team lead) see every request for the project.",
5556
+ "Developers see only their own requests."
5557
+ ],
5558
+ keywords: ["requests", "approval", "review", "pending"],
5559
+ topLevel: true,
5560
+ createCommand: () => requestsCommand
5561
+ },
5041
5562
  {
5042
5563
  id: "run",
5043
5564
  title: "Run command with secrets",
package/dist/index.js CHANGED
@@ -4,18 +4,18 @@ import {
4
4
  getTopLevelCommandCatalog,
5
5
  initSentry,
6
6
  openTUI
7
- } from "./chunk-SQRQ5VWJ.js";
7
+ } from "./chunk-QYRSBLFW.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.9.1" : "0.0.0";
13
+ var CLI_VERSION = true ? "1.10.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.9.1" : "0.0.0";
18
+ var CLI_VERSION2 = true ? "1.10.0" : "0.0.0";
19
19
  var CHECK_INTERVAL = 60 * 60 * 1e3;
20
20
  var _cache = null;
21
21
  function getCache() {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envpilot/cli",
3
- "version": "1.9.1",
3
+ "version": "1.10.0",
4
4
  "description": "Envpilot CLI — sync and manage environment variables from the terminal",
5
5
  "type": "module",
6
6
  "bin": {