@envpilot/cli 1.13.1 → 1.14.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-XDPWQLB5.js";
7
+ } from "./chunk-LKHOS64H.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.13.1" : "0.0.0",
10
+ release: true ? "1.14.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" } },
@@ -463,7 +463,7 @@ import { jsx } from "react/jsx-runtime";
463
463
  async function openTUI() {
464
464
  const [{ render }, { CLIApp }, { PressAnyKey }] = await Promise.all([
465
465
  import("ink"),
466
- import("./app-Q4OBTYMS.js"),
466
+ import("./app-AE2JWW2K.js"),
467
467
  import("./press-any-key-64XFP4O2.js")
468
468
  ]);
469
469
  while (true) {
@@ -1040,7 +1040,10 @@ var refs = {
1040
1040
  deviceSessionRecord: fnRef("deviceSessions:record"),
1041
1041
  deviceSessionRevoke: fnRef(
1042
1042
  "deviceSessions:revoke"
1043
- )
1043
+ ),
1044
+ pullValues: fnRef("variableValues:pullValues"),
1045
+ pushBulk: fnRef("variableValues:pushBulk"),
1046
+ createVariableRequest: fnRef("variableRequests:createWithValue")
1044
1047
  };
1045
1048
  async function convexQuery(ref, ...args) {
1046
1049
  const client = await getConvexClient();
@@ -1050,6 +1053,10 @@ async function convexMutation(ref, ...args) {
1050
1053
  const client = await getConvexClient();
1051
1054
  return client.mutation(ref, ...args);
1052
1055
  }
1056
+ async function convexAction(ref, ...args) {
1057
+ const client = await getConvexClient();
1058
+ return client.action(ref, ...args);
1059
+ }
1053
1060
  async function recordDeviceSession(deviceName, sessionId) {
1054
1061
  try {
1055
1062
  await convexMutation(refs.deviceSessionRecord, {
@@ -1074,161 +1081,7 @@ function booleanFeature(resolved, key, fallback = false) {
1074
1081
  const value = resolved?.features?.[key]?.value;
1075
1082
  return typeof value === "boolean" ? value : fallback;
1076
1083
  }
1077
- function registrableDomain(hostname2) {
1078
- const parts = hostname2.toLowerCase().split(".").filter(Boolean);
1079
- if (parts.length <= 2) return parts.join(".");
1080
- return parts.slice(-2).join(".");
1081
- }
1082
- var MAX_MANUAL_REDIRECTS = 5;
1083
1084
  var APIClient = class {
1084
- baseUrl;
1085
- constructor(options) {
1086
- this.baseUrl = options?.baseUrl ?? getApiUrl();
1087
- }
1088
- // ── Vault HTTP path (decrypted secret values) ──────────────────────────
1089
- /**
1090
- * Follow 3xx redirects manually, re-attaching Authorization only within the
1091
- * same registrable domain (defends against the apex→www redirect that would
1092
- * otherwise drop the header and yield a bogus 401).
1093
- */
1094
- async fetchWithSafeRedirects(initialUrl, init2) {
1095
- let currentUrl = initialUrl;
1096
- let currentInit = { ...init2, redirect: "manual" };
1097
- for (let hop = 0; hop < MAX_MANUAL_REDIRECTS; hop++) {
1098
- const response = await fetch(currentUrl, currentInit);
1099
- if (response.status < 300 || response.status >= 400) {
1100
- return response;
1101
- }
1102
- const location = response.headers.get("location");
1103
- if (!location) return response;
1104
- const nextUrl = new URL(location, currentUrl);
1105
- const prevHost = new URL(currentUrl).hostname;
1106
- const sameSite = registrableDomain(nextUrl.hostname) === registrableDomain(prevHost);
1107
- const headers = new Headers(currentInit.headers);
1108
- if (!sameSite) headers.delete("Authorization");
1109
- let nextMethod = (currentInit.method || "GET").toUpperCase();
1110
- let nextBody = currentInit.body;
1111
- if (response.status === 301 || response.status === 302 || response.status === 303) {
1112
- if (nextMethod !== "GET" && nextMethod !== "HEAD") {
1113
- nextMethod = "GET";
1114
- nextBody = void 0;
1115
- headers.delete("Content-Type");
1116
- }
1117
- }
1118
- currentUrl = nextUrl.toString();
1119
- currentInit = {
1120
- ...currentInit,
1121
- method: nextMethod,
1122
- headers,
1123
- body: nextBody,
1124
- redirect: "manual"
1125
- };
1126
- }
1127
- throw new APIError(
1128
- `Too many redirects while calling ${initialUrl}`,
1129
- 0,
1130
- "TOO_MANY_REDIRECTS"
1131
- );
1132
- }
1133
- /** Perform an authed vault request carrying a fresh WorkOS JWT bearer. */
1134
- async vaultRequest(method, path, options) {
1135
- const token = await ensureFreshAccessToken();
1136
- const url = new URL(path, this.baseUrl);
1137
- if (options?.params) {
1138
- for (const [key, value] of Object.entries(options.params)) {
1139
- url.searchParams.set(key, value);
1140
- }
1141
- }
1142
- const response = await this.fetchWithSafeRedirects(url.toString(), {
1143
- method,
1144
- headers: {
1145
- "Content-Type": "application/json",
1146
- Authorization: `Bearer ${token}`
1147
- },
1148
- body: options?.body ? JSON.stringify(options.body) : void 0
1149
- });
1150
- return this.handleResponse(response);
1151
- }
1152
- isAuthRedirect(response, bodyText) {
1153
- const location = response.headers.get("location") || "";
1154
- const finalUrl = response.url || "";
1155
- const contentType = response.headers.get("content-type") || "";
1156
- const preview = (bodyText || "").slice(0, 512).toLowerCase();
1157
- return location.includes("authkit") || finalUrl.includes("authkit") || contentType.includes("text/html") && (preview.includes("authorization_session_id") || preview.includes("client_id=") || preview.includes("<!doctype html"));
1158
- }
1159
- async handleResponse(response) {
1160
- if (!response.ok) {
1161
- await this.handleError(response);
1162
- }
1163
- const contentType = response.headers.get("content-type") || "";
1164
- const body = await response.text();
1165
- if (!contentType.includes("application/json")) {
1166
- if (this.isAuthRedirect(response, body)) {
1167
- throw new APIError(
1168
- "Your CLI session is not authorized for this endpoint. Please run `envpilot login` and try again.",
1169
- 401,
1170
- "AUTH_REDIRECT"
1171
- );
1172
- }
1173
- const preview = body.replace(/\s+/g, " ").slice(0, 160);
1174
- throw new APIError(
1175
- `Expected JSON but got ${contentType || "unknown content type"} from ${response.url}. Response starts with: ${preview}`,
1176
- response.status || 500
1177
- );
1178
- }
1179
- try {
1180
- return JSON.parse(body);
1181
- } catch {
1182
- const preview = body.replace(/\s+/g, " ").slice(0, 160);
1183
- throw new APIError(
1184
- `Failed to parse JSON response from ${response.url}. Response starts with: ${preview}`,
1185
- response.status || 500
1186
- );
1187
- }
1188
- }
1189
- async handleError(response) {
1190
- const bodyText = await response.text();
1191
- let message = `Request failed with status ${response.status}`;
1192
- let code;
1193
- try {
1194
- const data = JSON.parse(bodyText);
1195
- message = data.error || data.message || message;
1196
- code = data.code;
1197
- } catch {
1198
- }
1199
- if (response.status === 401) {
1200
- if (this.isAuthRedirect(response, bodyText)) {
1201
- throw new APIError(
1202
- "Your CLI session is not authorized for this endpoint. Please run `envpilot login` and try again.",
1203
- 401,
1204
- "AUTH_REDIRECT"
1205
- );
1206
- }
1207
- throw new APIError(
1208
- message || "Authentication failed. Please run `envpilot login`.",
1209
- 401,
1210
- code || "UNAUTHORIZED"
1211
- );
1212
- }
1213
- if (response.status === 403 && code === "TIER_LIMIT_REACHED") {
1214
- throw new APIError(
1215
- message || "Tier limit reached. Run `envpilot usage` to see your plan limits.",
1216
- 403,
1217
- "TIER_LIMIT_REACHED"
1218
- );
1219
- }
1220
- if (response.status === 403) {
1221
- throw new APIError(message || "Access denied.", 403, code || "FORBIDDEN");
1222
- }
1223
- if (response.status === 402) {
1224
- throw new APIError(
1225
- message || "Tier limit reached. Run `envpilot usage` to see your plan limits.",
1226
- 402,
1227
- "PAYMENT_REQUIRED"
1228
- );
1229
- }
1230
- throw new APIError(message, response.status, code);
1231
- }
1232
1085
  // ============================================
1233
1086
  // High-level methods — DIRECT to Convex
1234
1087
  // ============================================
@@ -1405,24 +1258,53 @@ var APIClient = class {
1405
1258
  * Returns the variable list, the response meta (unified role / scope info),
1406
1259
  * and any keys that failed vault decryption (skipped server-side).
1407
1260
  */
1408
- async listVariables(projectId, environment, organizationId) {
1409
- const params = { projectId };
1410
- if (environment) params.environment = environment;
1411
- if (organizationId) params.organizationId = organizationId;
1412
- const response = await this.vaultRequest("GET", "/api/cli/variables", { params });
1261
+ async listVariables(projectId, environment, _organizationId) {
1262
+ const result = await convexAction(refs.pullValues, {
1263
+ projectId,
1264
+ ...environment ? { environment } : {}
1265
+ });
1266
+ const variables = result.variables.filter((row) => row.value !== "[DECRYPTION_FAILED]").map((row) => ({
1267
+ _id: row._id,
1268
+ key: row.key,
1269
+ value: row.value,
1270
+ // The CLI Variable type expects a single environment string. Echo the
1271
+ // requested one, else the first the variable belongs to.
1272
+ environment: environment ?? row.environments[0] ?? "development",
1273
+ projectId: row.projectId,
1274
+ description: row.description,
1275
+ isSensitive: row.isSensitive,
1276
+ version: row.version,
1277
+ createdAt: row.createdAt,
1278
+ updatedAt: row.updatedAt,
1279
+ access: row.access
1280
+ }));
1281
+ const meta = {
1282
+ total: variables.length,
1283
+ environment: environment ?? "all",
1284
+ role: result.meta.role,
1285
+ projectRole: result.meta.projectRole,
1286
+ unifiedRole: result.meta.unifiedRole,
1287
+ assigned: result.meta.assigned,
1288
+ grantOnly: result.meta.grantOnly,
1289
+ environmentScope: result.meta.environmentScope,
1290
+ hasWriteAccess: result.meta.hasWriteAccess,
1291
+ scopeRestricted: result.meta.scopeRestricted,
1292
+ decryptionFailures: result.meta.decryptionFailures
1293
+ };
1413
1294
  return {
1414
- variables: response.data || [],
1415
- meta: response.meta,
1416
- decryptionFailures: response.meta?.decryptionFailures ?? []
1295
+ variables,
1296
+ meta,
1297
+ decryptionFailures: result.meta.decryptionFailures ?? []
1417
1298
  };
1418
1299
  }
1419
1300
  /** Bulk create/update variables (vault path — encrypts values server-side). */
1420
1301
  async bulkUpsertVariables(data) {
1421
- const response = await this.vaultRequest("POST", "/api/cli/variables/bulk", { body: data });
1422
- if (!response.data) {
1423
- throw new APIError("No result returned by server", 500);
1424
- }
1425
- return response.data;
1302
+ return convexAction(refs.pushBulk, {
1303
+ projectId: data.projectId,
1304
+ environment: data.environment,
1305
+ variables: data.variables,
1306
+ mode: data.mode
1307
+ });
1426
1308
  }
1427
1309
  /**
1428
1310
  * Submit a variable request (developers only). Goes over the vault path
@@ -1430,11 +1312,18 @@ var APIClient = class {
1430
1312
  * owners/PMs/team leads get a 403 here and create variables directly.
1431
1313
  */
1432
1314
  async createVariableRequest(data) {
1433
- const response = await this.vaultRequest("POST", "/api/cli/variable-requests", { body: data });
1434
- if (!response.data) {
1315
+ const created = await convexAction(refs.createVariableRequest, {
1316
+ projectId: data.projectId,
1317
+ key: data.key,
1318
+ value: data.value,
1319
+ environments: data.environments,
1320
+ isSensitive: data.isSensitive,
1321
+ description: data.description
1322
+ });
1323
+ if (!created) {
1435
1324
  throw new APIError("No variable request returned by server", 500);
1436
1325
  }
1437
- return response.data.request;
1326
+ return created;
1438
1327
  }
1439
1328
  };
1440
1329
  function createAPIClient() {
package/dist/index.js CHANGED
@@ -6,13 +6,13 @@ import {
6
6
  getTopLevelCommandCatalog,
7
7
  initSentry,
8
8
  openTUI
9
- } from "./chunk-XDPWQLB5.js";
9
+ } from "./chunk-LKHOS64H.js";
10
10
 
11
11
  // src/lib/program.ts
12
12
  import { Command } from "commander";
13
13
 
14
14
  // src/lib/cli-version.ts
15
- var CLI_VERSION = true ? "1.13.1" : "0.0.0";
15
+ var CLI_VERSION = true ? "1.14.0" : "0.0.0";
16
16
 
17
17
  // src/lib/version-check.ts
18
18
  import chalk from "chalk";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@envpilot/cli",
3
- "version": "1.13.1",
3
+ "version": "1.14.0",
4
4
  "description": "Envpilot CLI — sync and manage environment variables from the terminal",
5
5
  "type": "module",
6
6
  "bin": {