@envpilot/cli 1.9.1 → 1.11.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-AS4NIUYS.js";
8
8
 
9
9
  // src/ui/app.tsx
10
10
  import {
@@ -7,7 +7,10 @@ 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.11.0" : "0.0.0",
11
+ // All EnvPilot surfaces report to one Sentry project; the surface tag
12
+ // is how dashboards tell web / cli / extension events apart.
13
+ initialScope: { tags: { surface: "cli" } },
11
14
  // Free tier: disable performance monitoring
12
15
  tracesSampleRate: 0,
13
16
  beforeSend(event) {
@@ -469,7 +472,7 @@ import { jsx } from "react/jsx-runtime";
469
472
  async function openTUI() {
470
473
  const [{ render }, { CLIApp }, { PressAnyKey }] = await Promise.all([
471
474
  import("ink"),
472
- import("./app-3LJYJ4RK.js"),
475
+ import("./app-SRLCVHPE.js"),
473
476
  import("./press-any-key-64XFP4O2.js")
474
477
  ]);
475
478
  while (true) {
@@ -1169,6 +1172,29 @@ var APIClient = class {
1169
1172
  async bulkUpsertVariables(data) {
1170
1173
  return this.post("/api/cli/variables/bulk", data);
1171
1174
  }
1175
+ /**
1176
+ * Submit a variable request (developers only — owners/PMs/team leads
1177
+ * create variables directly and get a 403 from the server here).
1178
+ */
1179
+ async createVariableRequest(data) {
1180
+ const response = await this.post(
1181
+ "/api/cli/variable-requests",
1182
+ data
1183
+ );
1184
+ if (!response.data) {
1185
+ throw new APIError("No variable request returned by server", 500);
1186
+ }
1187
+ return response.data.request;
1188
+ }
1189
+ /**
1190
+ * List variable requests for a project, optionally filtered by status.
1191
+ */
1192
+ async listVariableRequests(projectId, status) {
1193
+ const params = { projectId };
1194
+ if (status) params.status = status;
1195
+ const response = await this.get("/api/cli/variable-requests", params);
1196
+ return response.data?.requests ?? [];
1197
+ }
1172
1198
  // ============================================
1173
1199
  // Authentication methods
1174
1200
  // ============================================
@@ -1457,6 +1483,32 @@ var projectConfigV2Schema = z.object({
1457
1483
  activeProjectId: z.string(),
1458
1484
  projects: z.array(projectEntrySchema).min(1)
1459
1485
  });
1486
+ var variableRequestStatusSchema = z.enum([
1487
+ "pending",
1488
+ "approved",
1489
+ "rejected",
1490
+ "canceled"
1491
+ ]);
1492
+ var variableRequestUserSchema = z.object({
1493
+ _id: z.string(),
1494
+ email: z.string().optional(),
1495
+ name: z.string().optional()
1496
+ }).nullable().optional();
1497
+ var variableRequestSchema = z.object({
1498
+ _id: z.string(),
1499
+ key: z.string(),
1500
+ description: z.string().optional(),
1501
+ environments: z.array(z.string()),
1502
+ projectId: z.string(),
1503
+ organizationId: z.string().optional(),
1504
+ isSensitive: z.boolean().optional(),
1505
+ status: variableRequestStatusSchema,
1506
+ reviewReason: z.string().optional(),
1507
+ createdAt: z.number(),
1508
+ updatedAt: z.number().optional(),
1509
+ requester: variableRequestUserSchema,
1510
+ reviewer: variableRequestUserSchema
1511
+ });
1460
1512
 
1461
1513
  // src/lib/project-config.ts
1462
1514
  var CONFIG_FILE_NAME = ".envpilot";
@@ -3757,8 +3809,8 @@ async function handlePath() {
3757
3809
  ]);
3758
3810
  }
3759
3811
  async function handleReset() {
3760
- const inquirer6 = await import("inquirer");
3761
- const { confirm } = await inquirer6.default.prompt([
3812
+ const inquirer7 = await import("inquirer");
3813
+ const { confirm } = await inquirer7.default.prompt([
3762
3814
  {
3763
3815
  type: "confirm",
3764
3816
  name: "confirm",
@@ -4895,6 +4947,443 @@ function createUICommand() {
4895
4947
  });
4896
4948
  }
4897
4949
 
4950
+ // src/commands/request.ts
4951
+ import { Command as Command17 } from "commander";
4952
+ import chalk17 from "chalk";
4953
+ import inquirer6 from "inquirer";
4954
+
4955
+ // src/lib/variable-requests.ts
4956
+ import { z as z3 } from "zod";
4957
+ var ALL_REQUEST_ENVIRONMENTS = [
4958
+ "development",
4959
+ "staging",
4960
+ "production"
4961
+ ];
4962
+ var requestKeySchema = z3.string().min(1, "Key is required").max(100, "Key must be 100 characters or less").regex(
4963
+ /^[A-Z][A-Z0-9_]*$/,
4964
+ "Key must be uppercase, start with a letter, and contain only letters, numbers, and underscores"
4965
+ );
4966
+ var requestValueSchema = z3.string().min(1, "Value is required");
4967
+ var requestDescriptionSchema = z3.string().max(500, "Description must be 500 characters or less");
4968
+ function validateRequestKey(key) {
4969
+ const result = requestKeySchema.safeParse(key);
4970
+ if (result.success) return { valid: true };
4971
+ return {
4972
+ valid: false,
4973
+ error: result.error.issues[0]?.message ?? "Invalid key"
4974
+ };
4975
+ }
4976
+ function validateRequestValue(value) {
4977
+ const result = requestValueSchema.safeParse(value);
4978
+ if (result.success) return { valid: true };
4979
+ return {
4980
+ valid: false,
4981
+ error: result.error.issues[0]?.message ?? "Invalid value"
4982
+ };
4983
+ }
4984
+ function validateRequestDescription(description) {
4985
+ if (description.length === 0) return { valid: true };
4986
+ const result = requestDescriptionSchema.safeParse(description);
4987
+ if (result.success) return { valid: true };
4988
+ return {
4989
+ valid: false,
4990
+ error: result.error.issues[0]?.message ?? "Invalid description"
4991
+ };
4992
+ }
4993
+ function allowedRequestEnvironments(environmentScope) {
4994
+ if (environmentScope == null) {
4995
+ return [...ALL_REQUEST_ENVIRONMENTS];
4996
+ }
4997
+ return ALL_REQUEST_ENVIRONMENTS.filter(
4998
+ (env) => environmentScope.includes(env)
4999
+ );
5000
+ }
5001
+ function buildCreateVariableRequestBody(input) {
5002
+ const description = input.description?.trim();
5003
+ return {
5004
+ projectId: input.projectId,
5005
+ key: input.key.trim(),
5006
+ value: input.value,
5007
+ ...description ? { description } : {},
5008
+ environments: input.environments,
5009
+ isSensitive: input.isSensitive ?? false
5010
+ };
5011
+ }
5012
+ function isRequestEligibleProject(project) {
5013
+ const role = normalizeOrgRole(project.unifiedRole ?? project.role);
5014
+ return role === "developer" && project.assigned === true;
5015
+ }
5016
+ function buildEligibleRequestTargets(projects, orgNameById) {
5017
+ return projects.filter(isRequestEligibleProject).map((p) => ({
5018
+ projectId: p._id,
5019
+ projectName: p.name,
5020
+ organizationId: p.organizationId,
5021
+ organizationName: orgNameById[p.organizationId] ?? p.organizationId,
5022
+ environmentScope: p.environmentScope ?? null
5023
+ }));
5024
+ }
5025
+ function formatProjectChoiceLabel(target) {
5026
+ return `${target.projectName} \u2014 ${target.organizationName}`;
5027
+ }
5028
+ function buildProjectChoices(targets) {
5029
+ return targets.map((t) => ({
5030
+ name: formatProjectChoiceLabel(t),
5031
+ value: t.projectId
5032
+ }));
5033
+ }
5034
+ function formatRequestContextBanner(input) {
5035
+ return `Requesting as ${input.email}
5036
+ Project: ${input.projectName} \xB7 Organization: ${input.organizationName}`;
5037
+ }
5038
+ var SUMMARY_LABEL_WIDTH = 15;
5039
+ function formatRequestSummary(input) {
5040
+ const rows = [
5041
+ ["Key:", input.key],
5042
+ [
5043
+ "Project:",
5044
+ formatProjectChoiceLabel({
5045
+ projectName: input.projectName,
5046
+ organizationName: input.organizationName
5047
+ })
5048
+ ],
5049
+ ["Environments:", input.environments.join(", ")],
5050
+ ["Sensitive:", input.isSensitive ? "yes" : "no"]
5051
+ ];
5052
+ return rows.map(([label, value]) => `${label.padEnd(SUMMARY_LABEL_WIDTH)}${value}`).join("\n");
5053
+ }
5054
+ function formatRequestSuccessMessage(input) {
5055
+ return `Request "${input.key}" submitted for ${input.projectName} (${input.organizationName}) \u2014 pending review.`;
5056
+ }
5057
+ function formatRequestsListHeader(input) {
5058
+ return `Requests for ${formatProjectChoiceLabel(input)}`;
5059
+ }
5060
+ function formatRequestRow(request) {
5061
+ return {
5062
+ key: request.key,
5063
+ environments: request.environments.join(", "),
5064
+ status: request.status,
5065
+ requested: new Date(request.createdAt).toLocaleDateString(),
5066
+ reason: request.reviewReason ?? ""
5067
+ };
5068
+ }
5069
+ function formatRequestRows(requests) {
5070
+ return requests.map(formatRequestRow);
5071
+ }
5072
+
5073
+ // src/commands/request.ts
5074
+ var requestCommand = new Command17("request").description(
5075
+ "Request creation of a new environment variable (developers only \u2014 owners, project managers, and team leads create variables directly)"
5076
+ ).option(
5077
+ "--project <name-or-id>",
5078
+ "Submit the request for a specific linked project"
5079
+ ).action(async (options) => {
5080
+ try {
5081
+ if (!isAuthenticated()) {
5082
+ throw notAuthenticated();
5083
+ }
5084
+ const email = getUser()?.email ?? "unknown account";
5085
+ const api = createAPIClient();
5086
+ const projectPassed = Boolean(options.project);
5087
+ const defaultEntry = resolveDefaultEntry(options.project);
5088
+ let target = null;
5089
+ let scope;
5090
+ if (defaultEntry) {
5091
+ const names = await resolveEntryNames(api, defaultEntry);
5092
+ const defaultTarget = {
5093
+ projectId: defaultEntry.projectId,
5094
+ projectName: names.projectName,
5095
+ organizationId: defaultEntry.organizationId,
5096
+ organizationName: names.organizationName
5097
+ };
5098
+ printBanner(email, defaultTarget);
5099
+ let useDefault = projectPassed;
5100
+ if (!projectPassed) {
5101
+ const { proceed } = await inquirer6.prompt([
5102
+ {
5103
+ type: "confirm",
5104
+ name: "proceed",
5105
+ message: "Request a variable for this project?",
5106
+ default: true
5107
+ }
5108
+ ]);
5109
+ useDefault = proceed;
5110
+ }
5111
+ if (useDefault) {
5112
+ const meta = await fetchProjectMeta(api, defaultEntry);
5113
+ const role = normalizeOrgRole(meta?.unifiedRole ?? meta?.role);
5114
+ if (role !== "developer") {
5115
+ warning(
5116
+ "You have direct write access to this project. Use `envpilot push` or create the variable directly instead of submitting a request."
5117
+ );
5118
+ return;
5119
+ }
5120
+ target = defaultTarget;
5121
+ scope = meta?.environmentScope;
5122
+ }
5123
+ }
5124
+ if (!target) {
5125
+ const picked = await pickRequestTarget(api);
5126
+ if (!picked) {
5127
+ info(
5128
+ "You have direct write access in your projects \u2014 create variables from the dashboard."
5129
+ );
5130
+ process.exit(0);
5131
+ }
5132
+ target = picked;
5133
+ scope = picked.environmentScope;
5134
+ printBanner(email, target);
5135
+ }
5136
+ const envChoices = allowedRequestEnvironments(scope);
5137
+ if (envChoices.length === 0) {
5138
+ error(
5139
+ "Your assignment does not include any environments in this project."
5140
+ );
5141
+ process.exit(1);
5142
+ }
5143
+ const answers = await inquirer6.prompt([
5144
+ {
5145
+ type: "input",
5146
+ name: "key",
5147
+ message: "Variable key (e.g. API_SECRET):",
5148
+ validate: (input) => {
5149
+ const result = validateRequestKey(input);
5150
+ return result.valid || result.error;
5151
+ }
5152
+ },
5153
+ {
5154
+ type: "password",
5155
+ name: "value",
5156
+ message: "Variable value:",
5157
+ mask: "*",
5158
+ validate: (input) => {
5159
+ const result = validateRequestValue(input);
5160
+ return result.valid || result.error;
5161
+ }
5162
+ },
5163
+ {
5164
+ type: "input",
5165
+ name: "description",
5166
+ message: "Description (optional):",
5167
+ default: "",
5168
+ validate: (input) => {
5169
+ const result = validateRequestDescription(input);
5170
+ return result.valid || result.error;
5171
+ }
5172
+ },
5173
+ {
5174
+ type: "checkbox",
5175
+ name: "environments",
5176
+ message: "Environments:",
5177
+ choices: envChoices,
5178
+ validate: (input) => input.length > 0 || "Select at least one environment"
5179
+ },
5180
+ {
5181
+ type: "confirm",
5182
+ name: "isSensitive",
5183
+ message: "Is this value sensitive?",
5184
+ default: true
5185
+ }
5186
+ ]);
5187
+ console.log();
5188
+ console.log(
5189
+ formatRequestSummary({
5190
+ key: answers.key.trim(),
5191
+ projectName: target.projectName,
5192
+ organizationName: target.organizationName,
5193
+ environments: answers.environments,
5194
+ isSensitive: answers.isSensitive
5195
+ })
5196
+ );
5197
+ console.log();
5198
+ const { submit } = await inquirer6.prompt([
5199
+ {
5200
+ type: "confirm",
5201
+ name: "submit",
5202
+ message: "Submit this request?",
5203
+ default: true
5204
+ }
5205
+ ]);
5206
+ if (!submit) {
5207
+ info("Request canceled.");
5208
+ return;
5209
+ }
5210
+ const body = buildCreateVariableRequestBody({
5211
+ projectId: target.projectId,
5212
+ key: answers.key,
5213
+ value: answers.value,
5214
+ description: answers.description,
5215
+ environments: answers.environments,
5216
+ isSensitive: answers.isSensitive
5217
+ });
5218
+ const created = await api.createVariableRequest(body);
5219
+ success(
5220
+ formatRequestSuccessMessage({
5221
+ key: created.key,
5222
+ projectName: target.projectName,
5223
+ organizationName: target.organizationName
5224
+ })
5225
+ );
5226
+ info(`Status: ${created.status} \xB7 Id: ${created._id}`);
5227
+ } catch (err) {
5228
+ if (err instanceof APIError && err.statusCode === 403) {
5229
+ error(err.message);
5230
+ return;
5231
+ }
5232
+ await handleError(err);
5233
+ }
5234
+ });
5235
+ function resolveDefaultEntry(projectOption) {
5236
+ const configV2 = readProjectConfigV2();
5237
+ if (!configV2) {
5238
+ if (projectOption) throw notInitialized();
5239
+ return null;
5240
+ }
5241
+ const project = resolveProject(configV2, projectOption);
5242
+ if (!project) {
5243
+ if (projectOption) {
5244
+ error(`Project not found: ${projectOption}`);
5245
+ console.log();
5246
+ console.log("Linked projects:");
5247
+ for (const p of configV2.projects) {
5248
+ console.log(` ${p.projectName || p.projectId} (${p.environment})`);
5249
+ }
5250
+ process.exit(1);
5251
+ }
5252
+ return null;
5253
+ }
5254
+ return project;
5255
+ }
5256
+ function printBanner(email, target) {
5257
+ console.log();
5258
+ console.log(
5259
+ chalk17.dim(
5260
+ formatRequestContextBanner({
5261
+ email,
5262
+ projectName: target.projectName,
5263
+ organizationName: target.organizationName
5264
+ })
5265
+ )
5266
+ );
5267
+ console.log();
5268
+ }
5269
+ async function resolveEntryNames(api, entry) {
5270
+ let projectName = entry.projectName;
5271
+ let organizationName = entry.organizationName;
5272
+ if (!projectName) {
5273
+ try {
5274
+ const project = await api.getProject(entry.projectId);
5275
+ projectName = project.name;
5276
+ } catch {
5277
+ }
5278
+ }
5279
+ if (!organizationName) {
5280
+ try {
5281
+ const orgs = await api.listOrganizations();
5282
+ organizationName = orgs.find((o) => o._id === entry.organizationId)?.name ?? "";
5283
+ } catch {
5284
+ }
5285
+ }
5286
+ return {
5287
+ projectName: projectName || entry.projectId,
5288
+ organizationName: organizationName || entry.organizationId
5289
+ };
5290
+ }
5291
+ async function fetchProjectMeta(api, entry) {
5292
+ const response = await api.get("/api/cli/variables", {
5293
+ projectId: entry.projectId,
5294
+ ...entry.organizationId && { organizationId: entry.organizationId }
5295
+ });
5296
+ return response.meta;
5297
+ }
5298
+ async function pickRequestTarget(api) {
5299
+ const targets = await withSpinner(
5300
+ "Finding projects you can request for...",
5301
+ async () => {
5302
+ const orgs = await api.listOrganizations();
5303
+ const orgNameById = Object.fromEntries(orgs.map((o) => [o._id, o.name]));
5304
+ const perOrg = await Promise.all(
5305
+ orgs.map((org) => api.listProjects(org._id))
5306
+ );
5307
+ return perOrg.flatMap(
5308
+ (projects) => buildEligibleRequestTargets(projects, orgNameById)
5309
+ );
5310
+ }
5311
+ );
5312
+ if (targets.length === 0) return null;
5313
+ const { projectId } = await inquirer6.prompt([
5314
+ {
5315
+ type: "list",
5316
+ name: "projectId",
5317
+ message: "Select a project to request for:",
5318
+ choices: buildProjectChoices(targets)
5319
+ }
5320
+ ]);
5321
+ return targets.find((t) => t.projectId === projectId) ?? null;
5322
+ }
5323
+
5324
+ // src/commands/requests.ts
5325
+ import { Command as Command18 } from "commander";
5326
+ var requestsCommand = new Command18("requests").description("List variable requests for a project").option(
5327
+ "--project <name-or-id>",
5328
+ "List requests for a specific linked project"
5329
+ ).option(
5330
+ "--status <status>",
5331
+ "Filter by status: pending, approved, rejected, canceled"
5332
+ ).action(async (options) => {
5333
+ try {
5334
+ if (!isAuthenticated()) {
5335
+ throw notAuthenticated();
5336
+ }
5337
+ const configV2 = readProjectConfigV2();
5338
+ if (!configV2) throw notInitialized();
5339
+ const project = resolveProject(configV2, options.project);
5340
+ if (!project) {
5341
+ error(`Project not found: ${options.project}`);
5342
+ console.log();
5343
+ console.log("Linked projects:");
5344
+ for (const p of configV2.projects) {
5345
+ console.log(` ${p.projectName || p.projectId} (${p.environment})`);
5346
+ }
5347
+ process.exit(1);
5348
+ }
5349
+ let status;
5350
+ if (options.status) {
5351
+ const parsed = variableRequestStatusSchema.safeParse(options.status);
5352
+ if (!parsed.success) {
5353
+ error(
5354
+ `Invalid status: ${options.status}. Must be one of pending, approved, rejected, canceled.`
5355
+ );
5356
+ process.exit(1);
5357
+ }
5358
+ status = parsed.data;
5359
+ }
5360
+ const api = createAPIClient();
5361
+ const requests = await withSpinner(
5362
+ "Fetching variable requests...",
5363
+ () => api.listVariableRequests(project.projectId, status)
5364
+ );
5365
+ header(
5366
+ formatRequestsListHeader({
5367
+ projectName: project.projectName || project.projectId,
5368
+ organizationName: project.organizationName || project.organizationId
5369
+ })
5370
+ );
5371
+ if (requests.length === 0) {
5372
+ info("No variable requests found.");
5373
+ return;
5374
+ }
5375
+ table(formatRequestRows(requests), [
5376
+ { key: "key", header: "KEY" },
5377
+ { key: "environments", header: "ENVIRONMENTS" },
5378
+ { key: "status", header: "STATUS" },
5379
+ { key: "requested", header: "REQUESTED" },
5380
+ { key: "reason", header: "REASON" }
5381
+ ]);
5382
+ } catch (err) {
5383
+ await handleError(err);
5384
+ }
5385
+ });
5386
+
4898
5387
  // src/lib/command-catalog.ts
4899
5388
  function formatArgv(argv) {
4900
5389
  return ["envpilot", ...argv].join(" ").trim();
@@ -5038,6 +5527,41 @@ var COMMAND_CATALOG = [
5038
5527
  topLevel: true,
5039
5528
  createCommand: () => pushCommand
5040
5529
  },
5530
+ {
5531
+ id: "request",
5532
+ title: "Request a new variable",
5533
+ category: "Sync",
5534
+ description: "Submit a request to create a new environment variable for review (developers only).",
5535
+ argv: ["request"],
5536
+ args: "[--project <name-or-id>]",
5537
+ examples: [["request"], ["request", "--project", "api"]],
5538
+ websiteSurface: "Maps to `/api/cli/variable-requests` (POST).",
5539
+ notes: [
5540
+ "Only assigned developers can submit requests \u2014 owners, project managers, and team leads create variables directly.",
5541
+ "Environment choices are limited to the developer's assigned environment scope.",
5542
+ "An owner, project manager, or team lead must approve the request before the variable is created."
5543
+ ],
5544
+ keywords: ["request", "approval", "developer", "create"],
5545
+ topLevel: true,
5546
+ createCommand: () => requestCommand
5547
+ },
5548
+ {
5549
+ id: "requests",
5550
+ title: "List variable requests",
5551
+ category: "Browse",
5552
+ description: "List pending and past variable requests for a project.",
5553
+ argv: ["requests"],
5554
+ args: "[--project <name-or-id>] [--status <status>]",
5555
+ examples: [["requests"], ["requests", "--status", "pending"]],
5556
+ websiteSurface: "Maps to `/api/cli/variable-requests` (GET).",
5557
+ notes: [
5558
+ "Reviewers (owner, assigned project manager/team lead) see every request for the project.",
5559
+ "Developers see only their own requests."
5560
+ ],
5561
+ keywords: ["requests", "approval", "review", "pending"],
5562
+ topLevel: true,
5563
+ createCommand: () => requestsCommand
5564
+ },
5041
5565
  {
5042
5566
  id: "run",
5043
5567
  title: "Run command with secrets",
@@ -5305,6 +5829,8 @@ function findCommandDefinition(commandIdOrName) {
5305
5829
 
5306
5830
  export {
5307
5831
  initSentry,
5832
+ captureError,
5833
+ flushSentry,
5308
5834
  getApiUrl,
5309
5835
  getUser,
5310
5836
  isAuthenticated,
package/dist/index.js CHANGED
@@ -1,21 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
+ captureError,
4
+ flushSentry,
3
5
  getApiUrl,
4
6
  getTopLevelCommandCatalog,
5
7
  initSentry,
6
8
  openTUI
7
- } from "./chunk-SQRQ5VWJ.js";
9
+ } from "./chunk-AS4NIUYS.js";
8
10
 
9
11
  // src/lib/program.ts
10
12
  import { Command } from "commander";
11
13
 
12
14
  // src/lib/cli-version.ts
13
- var CLI_VERSION = true ? "1.9.1" : "0.0.0";
15
+ var CLI_VERSION = true ? "1.11.0" : "0.0.0";
14
16
 
15
17
  // src/lib/version-check.ts
16
18
  import chalk from "chalk";
17
19
  import Conf from "conf";
18
- var CLI_VERSION2 = true ? "1.9.1" : "0.0.0";
20
+ var CLI_VERSION2 = true ? "1.11.0" : "0.0.0";
19
21
  var CHECK_INTERVAL = 60 * 60 * 1e3;
20
22
  var _cache = null;
21
23
  function getCache() {
@@ -89,7 +91,9 @@ async function main() {
89
91
  await program.parseAsync();
90
92
  }
91
93
  }
92
- main().catch((err) => {
94
+ main().catch(async (err) => {
93
95
  console.error(err);
96
+ captureError(err, { phase: "top-level" });
97
+ await flushSentry();
94
98
  process.exit(1);
95
99
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envpilot/cli",
3
- "version": "1.9.1",
3
+ "version": "1.11.0",
4
4
  "description": "Envpilot CLI — sync and manage environment variables from the terminal",
5
5
  "type": "module",
6
6
  "bin": {