@envpilot/cli 1.16.0 → 1.18.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-YBF26DSR.js";
7
+ } from "./chunk-DYZH5VNC.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.16.0" : "0.0.0",
10
+ release: true ? "1.18.0" : "0.0.0",
11
11
  // All EnvPilot surfaces report to one Sentry project; the surface tag
12
12
  // is how dashboards tell web / cli / extension events apart.
13
13
  initialScope: { tags: { surface: "cli" } },
@@ -54,16 +54,12 @@ import Conf from "conf";
54
54
 
55
55
  // src/lib/roles.ts
56
56
  var ROLE_LEVEL = {
57
- owner: 4,
58
- project_manager: 3,
59
- team_lead: 2,
60
- developer: 1
61
- };
62
- var ORG_ROLE_LABELS = {
63
- owner: "Owner",
64
- project_manager: "Project Manager",
65
- team_lead: "Team Lead",
66
- developer: "Developer"
57
+ owner: 100,
58
+ project_manager: 80,
59
+ team_lead: 60,
60
+ editor: 50,
61
+ developer: 40,
62
+ viewer: 20
67
63
  };
68
64
  function normalizeOrgRole(role) {
69
65
  switch (role) {
@@ -71,20 +67,20 @@ function normalizeOrgRole(role) {
71
67
  return "owner";
72
68
  case "member":
73
69
  return "developer";
74
- case "owner":
75
- case "project_manager":
76
- case "team_lead":
77
- case "developer":
78
- return role;
79
- default:
70
+ case void 0:
71
+ case null:
72
+ case "":
80
73
  return "developer";
74
+ default:
75
+ return role;
81
76
  }
82
77
  }
83
78
  function roleLevel(role) {
84
- return ROLE_LEVEL[normalizeOrgRole(role)];
79
+ return ROLE_LEVEL[normalizeOrgRole(role)] ?? 0;
85
80
  }
86
- function formatRoleLabel(role) {
87
- return ORG_ROLE_LABELS[normalizeOrgRole(role)];
81
+ function formatRoleLabel(role, meta) {
82
+ if (meta?.displayName) return meta.displayName;
83
+ return normalizeOrgRole(role).split("_").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
88
84
  }
89
85
  function isFileWritable(access) {
90
86
  if (access.role === "owner") return true;
@@ -93,7 +89,7 @@ function isFileWritable(access) {
93
89
  case "project_manager":
94
90
  case "team_lead":
95
91
  return true;
96
- case "developer":
92
+ default:
97
93
  return access.hasWriteAccess;
98
94
  }
99
95
  }
@@ -196,6 +192,14 @@ function extendCacheFreshness(projectId, environment, organizationId) {
196
192
  } catch {
197
193
  }
198
194
  }
195
+ function deleteCache(projectId, environment, organizationId) {
196
+ try {
197
+ const key = getCacheKey(projectId, environment, organizationId);
198
+ const path = getCachePath(key);
199
+ if (existsSync(path)) unlinkSync(path);
200
+ } catch {
201
+ }
202
+ }
199
203
  function clearAllCache() {
200
204
  let count = 0;
201
205
  try {
@@ -466,7 +470,7 @@ function isInteractiveTerminal() {
466
470
  async function openTUI() {
467
471
  const [{ render }, { CLIApp }, { PressAnyKey }] = await Promise.all([
468
472
  import("ink"),
469
- import("./app-HQ5P6QE6.js"),
473
+ import("./app-JYN7XJXA.js"),
470
474
  import("./press-any-key-64XFP4O2.js")
471
475
  ]);
472
476
  while (true) {
@@ -614,6 +618,8 @@ function formatRole(role) {
614
618
  return chalk.blue(label);
615
619
  case "developer":
616
620
  return chalk.yellow(label);
621
+ default:
622
+ return label;
617
623
  }
618
624
  }
619
625
  function roleNotice(role) {
@@ -665,7 +671,17 @@ var ErrorCodes = {
665
671
  INVALID_INPUT: "INVALID_INPUT",
666
672
  UNKNOWN_ERROR: "UNKNOWN_ERROR"
667
673
  };
674
+ var ACCESS_SUSPENDED_TOKEN = "ACCESS_SUSPENDED";
675
+ var ACCESS_REVOKED_MESSAGE = "Your access to this organization has been revoked. Please contact your organization administrator.";
668
676
  function formatError(error2) {
677
+ const data = error2?.data;
678
+ const raw = [
679
+ error2 instanceof Error ? error2.message : error2 ? String(error2) : "",
680
+ typeof data === "string" ? data : ""
681
+ ].join(" ");
682
+ if (raw.includes(ACCESS_SUSPENDED_TOKEN)) {
683
+ return chalk2.red(`Error: ${ACCESS_REVOKED_MESSAGE}`);
684
+ }
669
685
  if (error2 instanceof CLIError) {
670
686
  let message = chalk2.red(`Error: ${error2.message}`);
671
687
  if (error2.suggestion) {
@@ -731,6 +747,26 @@ function fileNotFound(path) {
731
747
  function invalidInput(message) {
732
748
  return new CLIError(message, ErrorCodes.INVALID_INPUT);
733
749
  }
750
+ function isConnectivityError(error2) {
751
+ const code = error2?.code ?? "";
752
+ if ([
753
+ "ECONNREFUSED",
754
+ "ECONNRESET",
755
+ "ENOTFOUND",
756
+ "ETIMEDOUT",
757
+ "EAI_AGAIN",
758
+ "ENETUNREACH",
759
+ "EHOSTUNREACH",
760
+ "UND_ERR_CONNECT_TIMEOUT"
761
+ ].includes(code)) {
762
+ return true;
763
+ }
764
+ if (error2?.status !== void 0) return false;
765
+ const message = error2 instanceof Error ? error2.message : String(error2);
766
+ return /fetch failed|network|socket hang up|getaddrinfo|connect timeout/i.test(
767
+ message
768
+ );
769
+ }
734
770
 
735
771
  // src/lib/auth-flow.ts
736
772
  import open from "open";
@@ -1292,7 +1328,8 @@ var APIClient = class {
1292
1328
  environmentScope: result.meta.environmentScope,
1293
1329
  hasWriteAccess: result.meta.hasWriteAccess,
1294
1330
  scopeRestricted: result.meta.scopeRestricted,
1295
- decryptionFailures: result.meta.decryptionFailures
1331
+ decryptionFailures: result.meta.decryptionFailures,
1332
+ capabilities: result.meta.capabilities
1296
1333
  };
1297
1334
  return {
1298
1335
  variables,
@@ -1528,7 +1565,9 @@ var projectSchema = z2.object({
1528
1565
  // Unified-role fields (optional so legacy server responses still parse)
1529
1566
  unifiedRole: z2.string().nullable().optional(),
1530
1567
  assigned: z2.boolean().optional(),
1531
- environmentScope: z2.array(z2.string()).nullable().optional()
1568
+ environmentScope: z2.array(z2.string()).nullable().optional(),
1569
+ // Resolved capability map (additive; absent on older deployments).
1570
+ capabilities: z2.record(z2.string(), z2.boolean()).nullable().optional()
1532
1571
  });
1533
1572
  var variableTagSchema = z2.object({
1534
1573
  _id: z2.string(),
@@ -1561,7 +1600,9 @@ var variablesMetaSchema = z2.object({
1561
1600
  grantOnly: z2.boolean().optional(),
1562
1601
  environmentScope: z2.array(z2.string()).nullable().optional(),
1563
1602
  hasWriteAccess: z2.boolean().optional(),
1564
- scopeRestricted: z2.boolean().optional()
1603
+ scopeRestricted: z2.boolean().optional(),
1604
+ // Resolved capability map (additive; absent on older deployments).
1605
+ capabilities: z2.record(z2.string(), z2.boolean()).optional()
1565
1606
  }).passthrough();
1566
1607
  var environmentSchema = z2.enum([
1567
1608
  "development",
@@ -4781,14 +4822,19 @@ var runCommand = new Command14("run").description(
4781
4822
  serverFingerprint
4782
4823
  );
4783
4824
  }
4784
- } catch {
4785
- variables = probe.entry.variables;
4786
- cacheHit = true;
4787
- cacheAge = formatAge(probe.entry.fetchedAt);
4788
- if (!options.quiet) {
4789
- warning(
4790
- `Using offline cache (age ${cacheAge}) \u2014 could not reach the server to verify freshness.`
4791
- );
4825
+ } catch (err) {
4826
+ if (isConnectivityError(err)) {
4827
+ variables = probe.entry.variables;
4828
+ cacheHit = true;
4829
+ cacheAge = formatAge(probe.entry.fetchedAt);
4830
+ if (!options.quiet) {
4831
+ warning(
4832
+ `Using offline cache (age ${cacheAge}) \u2014 could not reach the server to verify freshness.`
4833
+ );
4834
+ }
4835
+ } else {
4836
+ deleteCache(project.projectId, environment, organizationId);
4837
+ throw err;
4792
4838
  }
4793
4839
  }
4794
4840
  } else {
@@ -5113,9 +5159,17 @@ function buildCreateVariableRequestBody(input) {
5113
5159
  isSensitive: input.isSensitive ?? false
5114
5160
  };
5115
5161
  }
5162
+ function canSubmitRequests(input) {
5163
+ if (input.capabilities) {
5164
+ return input.capabilities["project.requests.submit"] === true;
5165
+ }
5166
+ return normalizeOrgRole(input.role) === "developer";
5167
+ }
5116
5168
  function isRequestEligibleProject(project) {
5117
- const role = normalizeOrgRole(project.unifiedRole ?? project.role);
5118
- return role === "developer" && project.assigned === true;
5169
+ return canSubmitRequests({
5170
+ capabilities: project.capabilities,
5171
+ role: project.unifiedRole ?? project.role
5172
+ }) && project.assigned === true;
5119
5173
  }
5120
5174
  function buildEligibleRequestTargets(projects, orgNameById) {
5121
5175
  return projects.filter(isRequestEligibleProject).map((p) => ({
@@ -5214,8 +5268,11 @@ var requestCommand = new Command17("request").description(
5214
5268
  }
5215
5269
  if (useDefault) {
5216
5270
  const meta = await fetchProjectMeta(api, defaultEntry);
5217
- const role = normalizeOrgRole(meta?.unifiedRole ?? meta?.role);
5218
- if (role !== "developer") {
5271
+ const eligible = canSubmitRequests({
5272
+ capabilities: meta?.capabilities,
5273
+ role: meta?.unifiedRole ?? meta?.role
5274
+ });
5275
+ if (!eligible) {
5219
5276
  warning(
5220
5277
  "You have direct write access to this project. Use `envpilot push` or create the variable directly instead of submitting a request."
5221
5278
  );
@@ -5329,10 +5386,6 @@ var requestCommand = new Command17("request").description(
5329
5386
  );
5330
5387
  info(`Status: ${created.status} \xB7 Id: ${created._id}`);
5331
5388
  } catch (err) {
5332
- if (err instanceof APIError && err.statusCode === 403) {
5333
- error(err.message);
5334
- return;
5335
- }
5336
5389
  await handleError(err);
5337
5390
  }
5338
5391
  });
package/dist/index.js CHANGED
@@ -7,13 +7,13 @@ import {
7
7
  initSentry,
8
8
  isInteractiveTerminal,
9
9
  openTUI
10
- } from "./chunk-YBF26DSR.js";
10
+ } from "./chunk-DYZH5VNC.js";
11
11
 
12
12
  // src/lib/program.ts
13
13
  import { Command } from "commander";
14
14
 
15
15
  // src/lib/cli-version.ts
16
- var CLI_VERSION = true ? "1.16.0" : "0.0.0";
16
+ var CLI_VERSION = true ? "1.18.0" : "0.0.0";
17
17
 
18
18
  // src/lib/version-check.ts
19
19
  import chalk from "chalk";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envpilot/cli",
3
- "version": "1.16.0",
3
+ "version": "1.18.0",
4
4
  "description": "Envpilot CLI — sync and manage environment variables from the terminal",
5
5
  "type": "module",
6
6
  "bin": {