@eve-horizon/cli 0.2.20 → 0.2.22

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.
Files changed (2) hide show
  1. package/dist/index.js +2328 -424
  2. package/package.json +7 -7
package/dist/index.js CHANGED
@@ -9811,8 +9811,8 @@ var require_sync = __commonJS({
9811
9811
  }
9812
9812
  return x;
9813
9813
  };
9814
- var defaultReadPackageSync = function defaultReadPackageSync2(readFileSync20, pkgfile) {
9815
- var body = readFileSync20(pkgfile);
9814
+ var defaultReadPackageSync = function defaultReadPackageSync2(readFileSync21, pkgfile) {
9815
+ var body = readFileSync21(pkgfile);
9816
9816
  try {
9817
9817
  var pkg = JSON.parse(body);
9818
9818
  return pkg;
@@ -9832,7 +9832,7 @@ var require_sync = __commonJS({
9832
9832
  }
9833
9833
  var opts = normalizeOptions(x, options);
9834
9834
  var isFile = opts.isFile || defaultIsFile;
9835
- var readFileSync20 = opts.readFileSync || fs4.readFileSync;
9835
+ var readFileSync21 = opts.readFileSync || fs4.readFileSync;
9836
9836
  var isDirectory = opts.isDirectory || defaultIsDir;
9837
9837
  var realpathSync = opts.realpathSync || defaultRealpathSync;
9838
9838
  var readPackageSync = opts.readPackageSync || defaultReadPackageSync;
@@ -9889,7 +9889,7 @@ var require_sync = __commonJS({
9889
9889
  if (!isFile(pkgfile)) {
9890
9890
  return loadpkg(path6.dirname(dir));
9891
9891
  }
9892
- var pkg = readPackageSync(readFileSync20, pkgfile);
9892
+ var pkg = readPackageSync(readFileSync21, pkgfile);
9893
9893
  if (pkg && opts.packageFilter) {
9894
9894
  pkg = opts.packageFilter(
9895
9895
  pkg,
@@ -9903,7 +9903,7 @@ var require_sync = __commonJS({
9903
9903
  var pkgfile = path6.join(maybeRealpathSync(realpathSync, x2, opts), "/package.json");
9904
9904
  if (isFile(pkgfile)) {
9905
9905
  try {
9906
- var pkg = readPackageSync(readFileSync20, pkgfile);
9906
+ var pkg = readPackageSync(readFileSync21, pkgfile);
9907
9907
  } catch (e) {
9908
9908
  }
9909
9909
  if (pkg && opts.packageFilter) {
@@ -13197,8 +13197,8 @@ var require_utils3 = __commonJS({
13197
13197
  const connectOptions = url;
13198
13198
  const protocol = getProtocol(connectOptions === null || connectOptions === void 0 ? void 0 : connectOptions.protocol);
13199
13199
  Object.assign(attributes, Object.assign({}, extractConnectionAttributeOrLog(url, semantic_conventions_1.SEMATTRS_MESSAGING_PROTOCOL, protocol, "protocol")));
13200
- const hostname = getHostname(connectOptions === null || connectOptions === void 0 ? void 0 : connectOptions.hostname);
13201
- Object.assign(attributes, Object.assign({}, extractConnectionAttributeOrLog(url, semantic_conventions_1.SEMATTRS_NET_PEER_NAME, hostname, "hostname")));
13200
+ const hostname2 = getHostname(connectOptions === null || connectOptions === void 0 ? void 0 : connectOptions.hostname);
13201
+ Object.assign(attributes, Object.assign({}, extractConnectionAttributeOrLog(url, semantic_conventions_1.SEMATTRS_NET_PEER_NAME, hostname2, "hostname")));
13202
13202
  const port = getPort(connectOptions.port, protocol);
13203
13203
  Object.assign(attributes, Object.assign({}, extractConnectionAttributeOrLog(url, semantic_conventions_1.SEMATTRS_NET_PEER_PORT, port, "port")));
13204
13204
  } else {
@@ -13208,8 +13208,8 @@ var require_utils3 = __commonJS({
13208
13208
  const urlParts = new URL(censoredUrl);
13209
13209
  const protocol = getProtocol(urlParts.protocol);
13210
13210
  Object.assign(attributes, Object.assign({}, extractConnectionAttributeOrLog(censoredUrl, semantic_conventions_1.SEMATTRS_MESSAGING_PROTOCOL, protocol, "protocol")));
13211
- const hostname = getHostname(urlParts.hostname);
13212
- Object.assign(attributes, Object.assign({}, extractConnectionAttributeOrLog(censoredUrl, semantic_conventions_1.SEMATTRS_NET_PEER_NAME, hostname, "hostname")));
13211
+ const hostname2 = getHostname(urlParts.hostname);
13212
+ Object.assign(attributes, Object.assign({}, extractConnectionAttributeOrLog(censoredUrl, semantic_conventions_1.SEMATTRS_NET_PEER_NAME, hostname2, "hostname")));
13213
13213
  const port = getPort(urlParts.port ? parseInt(urlParts.port) : void 0, protocol);
13214
13214
  Object.assign(attributes, Object.assign({}, extractConnectionAttributeOrLog(censoredUrl, semantic_conventions_1.SEMATTRS_NET_PEER_PORT, port, "port")));
13215
13215
  } catch (err) {
@@ -19754,7 +19754,7 @@ var require_OpenTelemetryBunyanStream = __commonJS({
19754
19754
  msg,
19755
19755
  v,
19756
19756
  // eslint-disable-line @typescript-eslint/no-unused-vars
19757
- hostname,
19757
+ hostname: hostname2,
19758
19758
  // eslint-disable-line @typescript-eslint/no-unused-vars
19759
19759
  pid,
19760
19760
  // eslint-disable-line @typescript-eslint/no-unused-vars
@@ -21171,9 +21171,9 @@ var require_instrumentation9 = __commonJS({
21171
21171
  */
21172
21172
  _getPatchLookupFunction(original) {
21173
21173
  const plugin = this;
21174
- return function patchedLookup(hostname, ...args) {
21175
- if (utils.isIgnored(hostname, plugin.getConfig().ignoreHostnames, (e) => api_1.diag.error("caught ignoreHostname error: ", e))) {
21176
- return original.apply(this, [hostname, ...args]);
21174
+ return function patchedLookup(hostname2, ...args) {
21175
+ if (utils.isIgnored(hostname2, plugin.getConfig().ignoreHostnames, (e) => api_1.diag.error("caught ignoreHostname error: ", e))) {
21176
+ return original.apply(this, [hostname2, ...args]);
21177
21177
  }
21178
21178
  const argsCount = args.length;
21179
21179
  api_1.diag.debug("wrap lookup callback function and starts span");
@@ -21184,7 +21184,7 @@ var require_instrumentation9 = __commonJS({
21184
21184
  const originalCallback = args[argsCount - 1];
21185
21185
  if (typeof originalCallback === "function") {
21186
21186
  args[argsCount - 1] = plugin._wrapLookupCallback(originalCallback, span);
21187
- return (0, instrumentation_1.safeExecuteInTheMiddle)(() => original.apply(this, [hostname, ...args]), (error) => {
21187
+ return (0, instrumentation_1.safeExecuteInTheMiddle)(() => original.apply(this, [hostname2, ...args]), (error) => {
21188
21188
  if (error != null) {
21189
21189
  utils.setError(error, span);
21190
21190
  span.end();
@@ -21192,7 +21192,7 @@ var require_instrumentation9 = __commonJS({
21192
21192
  });
21193
21193
  } else {
21194
21194
  const promise = (0, instrumentation_1.safeExecuteInTheMiddle)(() => original.apply(this, [
21195
- hostname,
21195
+ hostname2,
21196
21196
  ...args
21197
21197
  ]), (error) => {
21198
21198
  if (error != null) {
@@ -28558,8 +28558,8 @@ var require_utils14 = __commonJS({
28558
28558
  if (!pathname && optionsParsed.path) {
28559
28559
  pathname = url.parse(optionsParsed.path).pathname || "/";
28560
28560
  }
28561
- const hostname = optionsParsed.host || (optionsParsed.port != null ? `${optionsParsed.hostname}${optionsParsed.port}` : optionsParsed.hostname);
28562
- origin = `${optionsParsed.protocol || "http:"}//${hostname}`;
28561
+ const hostname2 = optionsParsed.host || (optionsParsed.port != null ? `${optionsParsed.hostname}${optionsParsed.port}` : optionsParsed.hostname);
28562
+ origin = `${optionsParsed.protocol || "http:"}//${hostname2}`;
28563
28563
  }
28564
28564
  const method = optionsParsed.method ? optionsParsed.method.toUpperCase() : "GET";
28565
28565
  return { origin, pathname, method, optionsParsed };
@@ -28579,7 +28579,7 @@ var require_utils14 = __commonJS({
28579
28579
  return { hostname: requestOptions.hostname, port: requestOptions.port };
28580
28580
  }
28581
28581
  const matches = ((_a = requestOptions.host) === null || _a === void 0 ? void 0 : _a.match(/^([^:/ ]+)(:\d{1,5})?/)) || null;
28582
- const hostname = requestOptions.hostname || (matches === null ? "localhost" : matches[1]);
28582
+ const hostname2 = requestOptions.hostname || (matches === null ? "localhost" : matches[1]);
28583
28583
  let port = requestOptions.port;
28584
28584
  if (!port) {
28585
28585
  if (matches && matches[2]) {
@@ -28588,12 +28588,12 @@ var require_utils14 = __commonJS({
28588
28588
  port = requestOptions.protocol === "https:" ? "443" : "80";
28589
28589
  }
28590
28590
  }
28591
- return { hostname, port };
28591
+ return { hostname: hostname2, port };
28592
28592
  };
28593
28593
  exports2.extractHostnameAndPort = extractHostnameAndPort;
28594
28594
  var getOutgoingRequestAttributes = (requestOptions, options, semconvStability) => {
28595
28595
  var _a, _b;
28596
- const hostname = options.hostname;
28596
+ const hostname2 = options.hostname;
28597
28597
  const port = options.port;
28598
28598
  const method = (_a = requestOptions.method) !== null && _a !== void 0 ? _a : "GET";
28599
28599
  const normalizedMethod = normalizeMethod(method);
@@ -28604,13 +28604,13 @@ var require_utils14 = __commonJS({
28604
28604
  [semantic_conventions_1.SEMATTRS_HTTP_URL]: urlFull,
28605
28605
  [semantic_conventions_1.SEMATTRS_HTTP_METHOD]: method,
28606
28606
  [semantic_conventions_1.SEMATTRS_HTTP_TARGET]: requestOptions.path || "/",
28607
- [semantic_conventions_1.SEMATTRS_NET_PEER_NAME]: hostname,
28608
- [semantic_conventions_1.SEMATTRS_HTTP_HOST]: (_b = headers.host) !== null && _b !== void 0 ? _b : `${hostname}:${port}`
28607
+ [semantic_conventions_1.SEMATTRS_NET_PEER_NAME]: hostname2,
28608
+ [semantic_conventions_1.SEMATTRS_HTTP_HOST]: (_b = headers.host) !== null && _b !== void 0 ? _b : `${hostname2}:${port}`
28609
28609
  };
28610
28610
  const newAttributes = {
28611
28611
  // Required attributes
28612
28612
  [semantic_conventions_1.ATTR_HTTP_REQUEST_METHOD]: normalizedMethod,
28613
- [semantic_conventions_1.ATTR_SERVER_ADDRESS]: hostname,
28613
+ [semantic_conventions_1.ATTR_SERVER_ADDRESS]: hostname2,
28614
28614
  [semantic_conventions_1.ATTR_SERVER_PORT]: Number(port),
28615
28615
  [semantic_conventions_1.ATTR_URL_FULL]: urlFull
28616
28616
  // leaving out protocol version, it is not yet negotiated
@@ -28785,7 +28785,7 @@ var require_utils14 = __commonJS({
28785
28785
  const httpVersion = request.httpVersion;
28786
28786
  const requestUrl = request.url ? url.parse(request.url) : null;
28787
28787
  const host = (requestUrl === null || requestUrl === void 0 ? void 0 : requestUrl.host) || headers.host;
28788
- const hostname = (requestUrl === null || requestUrl === void 0 ? void 0 : requestUrl.hostname) || (host === null || host === void 0 ? void 0 : host.replace(/^(.*)(:[0-9]{1,5})/, "$1")) || "localhost";
28788
+ const hostname2 = (requestUrl === null || requestUrl === void 0 ? void 0 : requestUrl.hostname) || (host === null || host === void 0 ? void 0 : host.replace(/^(.*)(:[0-9]{1,5})/, "$1")) || "localhost";
28789
28789
  const method = request.method;
28790
28790
  const normalizedMethod = normalizeMethod(method);
28791
28791
  const serverAddress = getServerAddress(request, options.component);
@@ -28815,7 +28815,7 @@ var require_utils14 = __commonJS({
28815
28815
  const oldAttributes = {
28816
28816
  [semantic_conventions_1.SEMATTRS_HTTP_URL]: (0, exports2.getAbsoluteUrl)(requestUrl, headers, `${options.component}:`),
28817
28817
  [semantic_conventions_1.SEMATTRS_HTTP_HOST]: host,
28818
- [semantic_conventions_1.SEMATTRS_NET_HOST_NAME]: hostname,
28818
+ [semantic_conventions_1.SEMATTRS_NET_HOST_NAME]: hostname2,
28819
28819
  [semantic_conventions_1.SEMATTRS_HTTP_METHOD]: method,
28820
28820
  [semantic_conventions_1.SEMATTRS_HTTP_SCHEME]: options.component
28821
28821
  };
@@ -29368,11 +29368,11 @@ var require_http = __commonJS({
29368
29368
  }, true)) {
29369
29369
  return original.apply(this, [optionsParsed, ...args]);
29370
29370
  }
29371
- const { hostname, port } = (0, utils_1.extractHostnameAndPort)(optionsParsed);
29371
+ const { hostname: hostname2, port } = (0, utils_1.extractHostnameAndPort)(optionsParsed);
29372
29372
  const attributes = (0, utils_1.getOutgoingRequestAttributes)(optionsParsed, {
29373
29373
  component,
29374
29374
  port,
29375
- hostname,
29375
+ hostname: hostname2,
29376
29376
  hookAttributes: instrumentation._callStartSpanHook(optionsParsed, instrumentation.getConfig().startOutgoingSpanHook)
29377
29377
  }, instrumentation._semconvStability);
29378
29378
  const startTime = (0, core_1.hrTime)();
@@ -36012,7 +36012,7 @@ var require_log_sending_utils = __commonJS({
36012
36012
  // depends on the user using the OpenTelemetry HostDetector and
36013
36013
  // ProcessDetector.
36014
36014
  // https://getpino.io/#/docs/api?id=opt-base
36015
- hostname,
36015
+ hostname: hostname2,
36016
36016
  // eslint-disable-line @typescript-eslint/no-unused-vars
36017
36017
  pid,
36018
36018
  // eslint-disable-line @typescript-eslint/no-unused-vars
@@ -39634,7 +39634,7 @@ var require_AlibabaCloudEcsDetector = __commonJS({
39634
39634
  /** Gets identity and host info and returns them as attribs. Empty object if fails */
39635
39635
  async _getAttributes(_config) {
39636
39636
  const { "owner-account-id": accountId, "instance-id": instanceId, "instance-type": instanceType, "region-id": region, "zone-id": availabilityZone } = await this._fetchIdentity();
39637
- const hostname = await this._fetchHost();
39637
+ const hostname2 = await this._fetchHost();
39638
39638
  return {
39639
39639
  [semantic_conventions_1.SEMRESATTRS_CLOUD_PROVIDER]: semantic_conventions_1.CLOUDPROVIDERVALUES_ALIBABA_CLOUD,
39640
39640
  [semantic_conventions_1.SEMRESATTRS_CLOUD_PLATFORM]: semantic_conventions_1.CLOUDPLATFORMVALUES_ALIBABA_CLOUD_ECS,
@@ -39643,7 +39643,7 @@ var require_AlibabaCloudEcsDetector = __commonJS({
39643
39643
  [semantic_conventions_1.SEMRESATTRS_CLOUD_AVAILABILITY_ZONE]: availabilityZone,
39644
39644
  [semantic_conventions_1.SEMRESATTRS_HOST_ID]: instanceId,
39645
39645
  [semantic_conventions_1.SEMRESATTRS_HOST_TYPE]: instanceType,
39646
- [semantic_conventions_1.SEMRESATTRS_HOST_NAME]: hostname
39646
+ [semantic_conventions_1.SEMRESATTRS_HOST_NAME]: hostname2
39647
39647
  };
39648
39648
  }
39649
39649
  /**
@@ -39888,7 +39888,7 @@ var require_AwsEc2DetectorSync = __commonJS({
39888
39888
  try {
39889
39889
  const token = await this._fetchToken();
39890
39890
  const { accountId, instanceId, instanceType, region, availabilityZone } = await this._fetchIdentity(token);
39891
- const hostname = await this._fetchHost(token);
39891
+ const hostname2 = await this._fetchHost(token);
39892
39892
  return {
39893
39893
  [semconv_1.ATTR_CLOUD_PROVIDER]: semconv_1.CLOUD_PROVIDER_VALUE_AWS,
39894
39894
  [semconv_1.ATTR_CLOUD_PLATFORM]: semconv_1.CLOUD_PLATFORM_VALUE_AWS_EC2,
@@ -39897,7 +39897,7 @@ var require_AwsEc2DetectorSync = __commonJS({
39897
39897
  [semconv_1.ATTR_CLOUD_AVAILABILITY_ZONE]: availabilityZone,
39898
39898
  [semconv_1.ATTR_HOST_ID]: instanceId,
39899
39899
  [semconv_1.ATTR_HOST_TYPE]: instanceType,
39900
- [semconv_1.ATTR_HOST_NAME]: hostname
39900
+ [semconv_1.ATTR_HOST_NAME]: hostname2
39901
39901
  };
39902
39902
  } catch (_a) {
39903
39903
  return {};
@@ -47946,7 +47946,7 @@ var require_GcpDetector = __commonJS({
47946
47946
  api_1.diag.debug("GcpDetector failed: GCP Metadata unavailable.");
47947
47947
  return {};
47948
47948
  }
47949
- const [projectId, instanceId, zoneId, clusterName, hostname] = await Promise.all([
47949
+ const [projectId, instanceId, zoneId, clusterName, hostname2] = await Promise.all([
47950
47950
  this._getProjectId(),
47951
47951
  this._getInstanceId(),
47952
47952
  this._getZone(),
@@ -47956,7 +47956,7 @@ var require_GcpDetector = __commonJS({
47956
47956
  const attributes = {};
47957
47957
  attributes[semantic_conventions_1.SEMRESATTRS_CLOUD_ACCOUNT_ID] = projectId;
47958
47958
  attributes[semantic_conventions_1.SEMRESATTRS_HOST_ID] = instanceId;
47959
- attributes[semantic_conventions_1.SEMRESATTRS_HOST_NAME] = hostname;
47959
+ attributes[semantic_conventions_1.SEMRESATTRS_HOST_NAME] = hostname2;
47960
47960
  attributes[semantic_conventions_1.SEMRESATTRS_CLOUD_AVAILABILITY_ZONE] = zoneId;
47961
47961
  attributes[semantic_conventions_1.SEMRESATTRS_CLOUD_PROVIDER] = semantic_conventions_1.CLOUDPROVIDERVALUES_GCP;
47962
47962
  if ((0, core_2.getEnv)().KUBERNETES_SERVICE_HOST)
@@ -48734,6 +48734,23 @@ function resolveContext(flags, credentials) {
48734
48734
  profileSource
48735
48735
  };
48736
48736
  }
48737
+ function resolveContextForProfile(profileName, profile, credentials) {
48738
+ const apiUrl = profile.api_url || DEFAULT_API_URL;
48739
+ const authKey = toAuthKey(apiUrl);
48740
+ const tokenEntry = credentials.tokens[authKey] || credentials.profiles?.[profileName];
48741
+ return {
48742
+ apiUrl,
48743
+ orgId: profile.org_id,
48744
+ projectId: profile.project_id,
48745
+ profileName,
48746
+ profile,
48747
+ authKey,
48748
+ token: tokenEntry?.access_token,
48749
+ refreshToken: tokenEntry?.refresh_token,
48750
+ expiresAt: tokenEntry?.expires_at,
48751
+ profileSource: "local"
48752
+ };
48753
+ }
48737
48754
  function normalizeRepoProfiles(parsed) {
48738
48755
  if (!parsed || typeof parsed !== "object") {
48739
48756
  return { profiles: {} };
@@ -48884,6 +48901,20 @@ var HELP = {
48884
48901
  "eve project sync --path ./custom-manifest.yaml"
48885
48902
  ]
48886
48903
  },
48904
+ status: {
48905
+ description: "Show deployment status across all profiles with revision info, service URLs, and deploy age",
48906
+ usage: "eve project status [--profile <name>] [--env <name>] [--json]",
48907
+ options: [
48908
+ "--profile <name> Show only this profile",
48909
+ "--env <name> Show only this environment",
48910
+ "--json Machine-readable JSON output"
48911
+ ],
48912
+ examples: [
48913
+ "eve project status",
48914
+ "eve project status --profile staging",
48915
+ "eve project status --env sandbox --json"
48916
+ ]
48917
+ },
48887
48918
  members: {
48888
48919
  description: "Manage project members (list, add, remove)",
48889
48920
  usage: "eve project members [list|add|remove] [options]",
@@ -49619,34 +49650,63 @@ for cloud deployments. Credentials are stored globally per API URL.`,
49619
49650
  subcommands: {
49620
49651
  can: {
49621
49652
  description: "Check if a principal can perform an action",
49622
- usage: "eve access can --org <org_id> --user <user_id> --permission <perm> [--project <project_id>]",
49653
+ usage: "eve access can --org <org_id> (--user <id>|--service-principal <id>|--group <id>) --permission <perm> [--project <project_id>] [--resource-type <type> --resource <id> --action <read|write|admin>]",
49623
49654
  options: [
49624
49655
  "--org <org_id> Org scope (uses profile default if omitted)",
49625
- "--user <user_id> User to check (mutually exclusive with --service-principal)",
49626
- "--service-principal <sp_id> Service principal to check (mutually exclusive with --user)",
49656
+ "--user <user_id> User to check (mutually exclusive with --service-principal/--group)",
49657
+ "--service-principal <sp_id> Service principal to check (mutually exclusive with --user/--group)",
49658
+ "--group <group_id> Group to check directly (mutually exclusive with --user/--service-principal)",
49627
49659
  "--permission <perm> Permission to check (e.g., chat:write, jobs:admin)",
49628
- "--project <project_id> Optional project scope for the check"
49660
+ "--project <project_id> Optional project scope for the check",
49661
+ "--resource-type <type> Optional resource type: orgfs|orgdocs|envdb",
49662
+ "--resource <id> Optional resource id/path (required when --resource-type used)",
49663
+ "--action <action> Optional action: read|write|admin"
49629
49664
  ],
49630
49665
  examples: [
49631
49666
  "eve access can --org org_xxx --user user_abc --permission chat:write",
49632
49667
  "eve access can --org org_xxx --user user_abc --project proj_xxx --permission jobs:admin",
49633
- "eve access can --org org_xxx --service-principal sp_xxx --permission jobs:read"
49668
+ "eve access can --org org_xxx --service-principal sp_xxx --permission jobs:read",
49669
+ "eve access can --org org_xxx --user user_abc --permission orgfs:read --resource-type orgfs --resource /groups/pm/spec.md --action read"
49634
49670
  ]
49635
49671
  },
49636
49672
  explain: {
49637
49673
  description: "Explain the permission resolution chain",
49638
- usage: "eve access explain --org <org_id> --user <user_id> --permission <perm> [--project <project_id>]",
49674
+ usage: "eve access explain --org <org_id> (--user <id>|--service-principal <id>|--group <id>) --permission <perm> [--project <project_id>] [--resource-type <type> --resource <id> --action <read|write|admin>]",
49639
49675
  options: [
49640
49676
  "--org <org_id> Org scope (uses profile default if omitted)",
49641
- "--user <user_id> User to explain (mutually exclusive with --service-principal)",
49642
- "--service-principal <sp_id> Service principal to explain (mutually exclusive with --user)",
49677
+ "--user <user_id> User to explain (mutually exclusive with --service-principal/--group)",
49678
+ "--service-principal <sp_id> Service principal to explain (mutually exclusive with --user/--group)",
49679
+ "--group <group_id> Group to explain directly (mutually exclusive with --user/--service-principal)",
49643
49680
  "--permission <perm> Permission to explain (e.g., chat:write, jobs:admin)",
49644
- "--project <project_id> Optional project scope for the explanation"
49681
+ "--project <project_id> Optional project scope for the explanation",
49682
+ "--resource-type <type> Optional resource type: orgfs|orgdocs|envdb",
49683
+ "--resource <id> Optional resource id/path (required when --resource-type used)",
49684
+ "--action <action> Optional action: read|write|admin"
49645
49685
  ],
49646
49686
  examples: [
49647
49687
  "eve access explain --org org_xxx --user user_abc --permission jobs:admin",
49648
49688
  "eve access explain --org org_xxx --user user_abc --project proj_xxx --permission jobs:admin",
49649
- "eve access explain --org org_xxx --service-principal sp_xxx --permission jobs:read"
49689
+ "eve access explain --org org_xxx --service-principal sp_xxx --permission jobs:read",
49690
+ "eve access explain --org org_xxx --user user_abc --permission orgfs:read --resource-type orgfs --resource /groups/pm/spec.md --action read"
49691
+ ]
49692
+ },
49693
+ groups: {
49694
+ description: "Manage access groups and members",
49695
+ usage: "eve access groups <create|list|show|update|delete|members> [args]",
49696
+ examples: [
49697
+ 'eve access groups create "Product Management" --org org_xxx --slug pm-team',
49698
+ "eve access groups list --org org_xxx",
49699
+ "eve access groups members add pm-team --org org_xxx --user user_abc",
49700
+ "eve access groups members list pm-team --org org_xxx"
49701
+ ]
49702
+ },
49703
+ memberships: {
49704
+ description: "Inspect memberships, effective bindings, and effective scopes for a principal",
49705
+ usage: "eve access memberships --org <org_id> (--user <id>|--service-principal <id>|--group <id>)",
49706
+ examples: [
49707
+ "eve access memberships --org org_xxx --user user_abc",
49708
+ "eve access memberships --org org_xxx --service-principal sp_abc",
49709
+ "eve access memberships --org org_xxx --group grp_abc"
49650
49710
  ]
49651
49711
  },
49652
49712
  validate: {
@@ -49904,10 +49964,15 @@ for cloud deployments. Credentials are stored globally per API URL.`,
49904
49964
  examples: ["eve db schema --env staging"]
49905
49965
  },
49906
49966
  rls: {
49907
- description: "Show RLS policies and tables for an environment",
49908
- usage: "eve db rls --env <name> [--project <id>]",
49909
- options: ["--env <name> Environment name (required)"],
49910
- examples: ["eve db rls --env staging"]
49967
+ description: "Show RLS policies or scaffold helper SQL",
49968
+ usage: "eve db rls --env <name> [--project <id>] | eve db rls init --with-groups [--out <path>] [--force]",
49969
+ options: [
49970
+ "--env <name> Environment name (required for inspect mode)",
49971
+ "--with-groups Generate app.current_group_ids()/app.has_group() helper SQL (init mode)",
49972
+ "--out <path> Output file for init mode (default: db/rls/helpers.sql)",
49973
+ "--force Overwrite output file when it already exists"
49974
+ ],
49975
+ examples: ["eve db rls --env staging", "eve db rls init --with-groups"]
49911
49976
  },
49912
49977
  sql: {
49913
49978
  description: "Run parameterized SQL as the calling user",
@@ -50663,6 +50728,33 @@ eve-new-project-setup skill to complete configuration.`,
50663
50728
  'eve docs query --org org_xxx --where "metadata.feature_status in draft,review"'
50664
50729
  ]
50665
50730
  },
50731
+ fs: {
50732
+ description: "Manage org filesystem sync links, events, and diagnostics.",
50733
+ usage: "eve fs sync <subcommand> [options]",
50734
+ subcommands: {
50735
+ sync: {
50736
+ description: "Org filesystem sync operations",
50737
+ usage: "eve fs sync <init|status|logs|pause|resume|disconnect|mode|conflicts|resolve|doctor> [options]",
50738
+ options: [
50739
+ "init: --org <id> --local <path> [--mode two-way|push-only|pull-only]",
50740
+ "status: --org <id>",
50741
+ "logs: --org <id> [--after <seq>] [--limit <n>] [--follow]",
50742
+ "pause|resume|disconnect: --org <id> [--link <link_id>]",
50743
+ "mode: --org <id> --set <two-way|push-only|pull-only> [--link <link_id>]",
50744
+ "conflicts: --org <id> [--open-only]",
50745
+ "resolve: --org <id> --conflict <id> --strategy <pick-remote|pick-local|manual>",
50746
+ "doctor: --org <id>"
50747
+ ]
50748
+ }
50749
+ },
50750
+ examples: [
50751
+ "eve fs sync init --org org_xxx --local ~/Eve/acme --mode two-way",
50752
+ "eve fs sync status --org org_xxx",
50753
+ "eve fs sync logs --org org_xxx --follow",
50754
+ "eve fs sync mode --org org_xxx --set pull-only",
50755
+ "eve fs sync doctor --org org_xxx"
50756
+ ]
50757
+ },
50666
50758
  resources: {
50667
50759
  description: "Resolve resource URIs into content snapshots.",
50668
50760
  usage: "eve resources <subcommand> [options]",
@@ -50951,6 +51043,7 @@ function showMainHelp() {
50951
51043
  console.log(" workflow Inspect manifest workflows (list, show)");
50952
51044
  console.log(" event Emit and inspect events (app integration)");
50953
51045
  console.log(" docs Manage org documents");
51046
+ console.log(" fs Manage org filesystem sync");
50954
51047
  console.log(" resources Resolve org/job resources");
50955
51048
  console.log(" secrets Manage secrets (project/org/user scope)");
50956
51049
  console.log(" harness Inspect harnesses and auth status");
@@ -51666,8 +51759,201 @@ async function handleProject(subcommand, positionals, flags, context2) {
51666
51759
  }
51667
51760
  return;
51668
51761
  }
51762
+ case "status":
51763
+ return handleStatus(flags, context2, json);
51669
51764
  default:
51670
- throw new Error("Usage: eve project <ensure|list|get|spend|update|sync|members|bootstrap>");
51765
+ throw new Error("Usage: eve project <ensure|list|get|spend|update|sync|members|bootstrap|status>");
51766
+ }
51767
+ }
51768
+ async function handleStatus(flags, currentContext, json) {
51769
+ const repoProfiles = loadRepoProfiles();
51770
+ const credentials = loadCredentials();
51771
+ const profileFilter = getStringFlag(flags, ["profile"]);
51772
+ const envFilter = getStringFlag(flags, ["env"]);
51773
+ const profileEntries = Object.entries(repoProfiles.profiles);
51774
+ if (profileEntries.length === 0) {
51775
+ throw new Error(
51776
+ "No profiles configured. Use `eve profile create <name> --api-url <url> --project <id>` to add one."
51777
+ );
51778
+ }
51779
+ const results = [];
51780
+ for (const [name, profile] of profileEntries) {
51781
+ if (profileFilter && name !== profileFilter) continue;
51782
+ const active = name === repoProfiles.activeProfile;
51783
+ const ctx = resolveContextForProfile(name, profile, credentials);
51784
+ if (!ctx.projectId) {
51785
+ results.push({ name, active, api_url: ctx.apiUrl, error: "No project_id configured" });
51786
+ continue;
51787
+ }
51788
+ if (!ctx.token) {
51789
+ results.push({
51790
+ name,
51791
+ active,
51792
+ api_url: ctx.apiUrl,
51793
+ project_id: ctx.projectId,
51794
+ error: "Not authenticated (run: eve auth login --profile " + name + ")"
51795
+ });
51796
+ continue;
51797
+ }
51798
+ try {
51799
+ const result = await fetchProfileStatus(ctx, envFilter);
51800
+ results.push({ name, active, ...result });
51801
+ } catch (err) {
51802
+ results.push({
51803
+ name,
51804
+ active,
51805
+ api_url: ctx.apiUrl,
51806
+ project_id: ctx.projectId,
51807
+ error: err instanceof Error ? err.message : String(err)
51808
+ });
51809
+ }
51810
+ }
51811
+ if (json) {
51812
+ outputJson({ profiles: results }, json);
51813
+ return;
51814
+ }
51815
+ formatStatusOutput(results);
51816
+ }
51817
+ async function fetchProfileStatus(ctx, envFilter) {
51818
+ const [project, releasesResponse] = await Promise.all([
51819
+ requestJson(ctx, `/projects/${ctx.projectId}`),
51820
+ requestJson(ctx, `/projects/${ctx.projectId}/releases?limit=50`).catch(() => ({ data: [] }))
51821
+ ]);
51822
+ const releasesById = new Map(releasesResponse.data.map((r) => [r.id, r]));
51823
+ const envResponse = await requestJson(ctx, `/projects/${ctx.projectId}/envs?limit=50`);
51824
+ const domain = inferDomain(ctx.apiUrl);
51825
+ const environments = [];
51826
+ for (const env of envResponse.data) {
51827
+ if (envFilter && env.name !== envFilter) continue;
51828
+ const status = env.suspended_at ? "suspended" : "active";
51829
+ let release = null;
51830
+ if (env.current_release_id) {
51831
+ const r = releasesById.get(env.current_release_id);
51832
+ if (r) {
51833
+ release = {
51834
+ git_sha: r.git_sha,
51835
+ version: r.version,
51836
+ tag: r.tag,
51837
+ deployed_at: r.created_at,
51838
+ created_by: r.created_by
51839
+ };
51840
+ }
51841
+ }
51842
+ if (env.suspended_at) {
51843
+ environments.push({
51844
+ name: env.name,
51845
+ type: env.type,
51846
+ status,
51847
+ namespace: env.namespace,
51848
+ release,
51849
+ services: []
51850
+ });
51851
+ continue;
51852
+ }
51853
+ let services = [];
51854
+ try {
51855
+ const diagnose = await requestJson(ctx, `/projects/${ctx.projectId}/envs/${env.name}/diagnose`);
51856
+ services = buildStatusServices(diagnose.pods, diagnose.namespace ?? env.namespace, domain);
51857
+ } catch {
51858
+ }
51859
+ environments.push({
51860
+ name: env.name,
51861
+ type: env.type,
51862
+ status,
51863
+ namespace: env.namespace,
51864
+ release,
51865
+ services
51866
+ });
51867
+ }
51868
+ return {
51869
+ api_url: ctx.apiUrl,
51870
+ project_id: project.id,
51871
+ project_name: project.name,
51872
+ environments
51873
+ };
51874
+ }
51875
+ function buildStatusServices(pods, namespace, domain) {
51876
+ const services = /* @__PURE__ */ new Map();
51877
+ for (const pod of pods) {
51878
+ const component = pod.labels["eve.component"] || pod.labels["app.kubernetes.io/name"] || pod.labels["app"] || pod.labels["component"] || "unknown";
51879
+ const existing = services.get(component) ?? { ready: 0, total: 0, phases: /* @__PURE__ */ new Set() };
51880
+ existing.total += 1;
51881
+ if (pod.ready) existing.ready += 1;
51882
+ existing.phases.add(pod.phase);
51883
+ services.set(component, existing);
51884
+ }
51885
+ return Array.from(services.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, info]) => {
51886
+ const allDone = info.phases.size > 0 && [...info.phases].every((p) => p === "Succeeded" || p === "Failed");
51887
+ const status = allDone ? "completed" : info.ready === info.total ? "ready" : "not-ready";
51888
+ const url = !allDone && namespace && domain ? buildServiceUrl(name, namespace, domain) : null;
51889
+ return {
51890
+ name,
51891
+ pods_ready: info.ready,
51892
+ pods_total: info.total,
51893
+ status,
51894
+ url
51895
+ };
51896
+ });
51897
+ }
51898
+ function inferDomain(apiUrl) {
51899
+ try {
51900
+ const host = new URL(apiUrl).hostname;
51901
+ if (host.startsWith("api.eve.")) return host.slice("api.eve.".length);
51902
+ if (host.startsWith("api.")) return host.slice("api.".length);
51903
+ return null;
51904
+ } catch {
51905
+ return null;
51906
+ }
51907
+ }
51908
+ function buildServiceUrl(component, namespace, domain) {
51909
+ const slug = namespace.startsWith("eve-") ? namespace.slice(4) : namespace;
51910
+ const secure = !domain.includes("lvh.me") && !domain.includes("localhost");
51911
+ return `${secure ? "https" : "http"}://${component}.${slug}.${domain}`;
51912
+ }
51913
+ function formatStatusOutput(results) {
51914
+ for (let i = 0; i < results.length; i++) {
51915
+ const r = results[i];
51916
+ if (i > 0) console.log("");
51917
+ const marker = r.active ? " (active)" : "";
51918
+ console.log(`${r.name}${marker} ${r.api_url}`);
51919
+ if (r.error) {
51920
+ console.log(` error: ${r.error}`);
51921
+ continue;
51922
+ }
51923
+ const label = r.project_name ? `${r.project_id} (${r.project_name})` : r.project_id;
51924
+ console.log(` project: ${label}`);
51925
+ if (!r.environments || r.environments.length === 0) {
51926
+ console.log(" (no environments)");
51927
+ continue;
51928
+ }
51929
+ for (const env of r.environments) {
51930
+ console.log("");
51931
+ console.log(` ${env.name} ${env.status} ${env.type}`);
51932
+ if (env.release) {
51933
+ const sha = env.release.git_sha.substring(0, 8);
51934
+ const age = formatAge(env.release.deployed_at);
51935
+ const ver = env.release.tag ?? env.release.version ?? "";
51936
+ const verPart = ver ? ` ${ver}` : "";
51937
+ console.log(` revision: ${sha}${verPart} deployed ${age}`);
51938
+ }
51939
+ if (env.services.length === 0) {
51940
+ if (env.status === "suspended") {
51941
+ console.log(" (suspended)");
51942
+ } else {
51943
+ console.log(" (no services)");
51944
+ }
51945
+ continue;
51946
+ }
51947
+ const nameW = Math.max(...env.services.map((s) => s.name.length));
51948
+ const podsW = Math.max(...env.services.map((s) => `${s.pods_ready}/${s.pods_total}`.length));
51949
+ for (const svc of env.services) {
51950
+ const pods = `${svc.pods_ready}/${svc.pods_total}`;
51951
+ const urlPart = svc.url ? ` ${svc.url}` : "";
51952
+ console.log(
51953
+ ` ${padRight(svc.name, nameW)} ${padRight(pods, podsW)} ${padRight(svc.status, 9)}${urlPart}`
51954
+ );
51955
+ }
51956
+ }
51671
51957
  }
51672
51958
  }
51673
51959
  function parseSinceValue2(since) {
@@ -51697,6 +51983,21 @@ function parseSinceValue2(since) {
51697
51983
  }
51698
51984
  return now.toISOString();
51699
51985
  }
51986
+ function formatAge(isoDate) {
51987
+ const ms = Date.now() - new Date(isoDate).getTime();
51988
+ if (ms < 0) return "just now";
51989
+ const secs = Math.floor(ms / 1e3);
51990
+ if (secs < 60) return `${secs}s ago`;
51991
+ const mins = Math.floor(secs / 60);
51992
+ if (mins < 60) return `${mins}m ago`;
51993
+ const hours = Math.floor(mins / 60);
51994
+ if (hours < 24) return `${hours}h ago`;
51995
+ const days = Math.floor(hours / 24);
51996
+ return `${days}d ago`;
51997
+ }
51998
+ function padRight(str, width) {
51999
+ return str.length >= width ? str : str + " ".repeat(width - str.length);
52000
+ }
51700
52001
  function buildQuery2(params) {
51701
52002
  const search = new URLSearchParams();
51702
52003
  Object.entries(params).forEach(([key, value]) => {
@@ -55834,6 +56135,8 @@ var configSchema = external_exports.object({
55834
56135
  EVE_GITHUB_TOKEN: external_exports.string().optional(),
55835
56136
  EVE_SLACK_SIGNING_SECRET: external_exports.string().optional(),
55836
56137
  EVE_INTERNAL_API_KEY: external_exports.string().optional(),
56138
+ EVE_ORG_FS_LINK_TOKEN_SECRET: external_exports.string().optional(),
56139
+ EVE_ORG_FS_LINK_TOKEN_TTL_SECONDS: external_exports.coerce.number().int().min(60).default(900),
55837
56140
  EVE_SECRETS_MASTER_KEY: external_exports.string().optional(),
55838
56141
  EVE_DEFAULT_DOMAIN: external_exports.string().optional(),
55839
56142
  // Cluster-level default domain for Ingress (e.g., lvh.me, apps.example.com)
@@ -56961,56 +57264,179 @@ var AccessRoleResponseSchema = external_exports.object({
56961
57264
  updated_at: external_exports.string()
56962
57265
  });
56963
57266
  var AccessRoleListResponseSchema = createApiListResponseSchema(AccessRoleResponseSchema);
57267
+ var AccessPrincipalTypeSchema = external_exports.enum(["user", "service_principal", "group"]);
57268
+ var AccessGroupMemberPrincipalTypeSchema = external_exports.enum(["user", "service_principal"]);
57269
+ var AccessScopePrefixesSchema = external_exports.object({
57270
+ allow_prefixes: external_exports.array(external_exports.string()).optional(),
57271
+ read_only_prefixes: external_exports.array(external_exports.string()).optional()
57272
+ }).strict();
57273
+ var AccessScopeEnvDbSchema = external_exports.object({
57274
+ schemas: external_exports.array(external_exports.string()).optional(),
57275
+ tables: external_exports.array(external_exports.string()).optional()
57276
+ }).strict();
57277
+ var AccessBindingScopeSchema = external_exports.object({
57278
+ orgfs: AccessScopePrefixesSchema.optional(),
57279
+ orgdocs: AccessScopePrefixesSchema.optional(),
57280
+ envdb: AccessScopeEnvDbSchema.optional()
57281
+ }).strict();
56964
57282
  var CreateAccessBindingRequestSchema = external_exports.object({
56965
57283
  role_name: external_exports.string().min(1),
56966
- principal_type: external_exports.enum(["user", "service_principal"]),
57284
+ principal_type: AccessPrincipalTypeSchema,
56967
57285
  principal_id: external_exports.string().min(1),
56968
- project_id: external_exports.string().optional()
57286
+ project_id: external_exports.string().optional(),
57287
+ scope_json: AccessBindingScopeSchema.optional()
56969
57288
  });
56970
57289
  var AccessBindingResponseSchema = external_exports.object({
56971
57290
  id: external_exports.string(),
56972
57291
  role_id: external_exports.string(),
56973
57292
  role_name: external_exports.string(),
56974
- principal_type: external_exports.string(),
57293
+ principal_type: AccessPrincipalTypeSchema,
56975
57294
  principal_id: external_exports.string(),
56976
57295
  project_id: external_exports.string().nullable(),
57296
+ scope_json: AccessBindingScopeSchema.nullable().optional(),
56977
57297
  created_by: external_exports.string().nullable(),
56978
57298
  created_at: external_exports.string()
56979
57299
  });
56980
57300
  var AccessBindingListResponseSchema = createApiListResponseSchema(AccessBindingResponseSchema);
57301
+ var CreateAccessGroupRequestSchema = external_exports.object({
57302
+ name: external_exports.string().min(1).max(100),
57303
+ slug: external_exports.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9-_]*$/).optional(),
57304
+ description: external_exports.string().max(500).optional()
57305
+ });
57306
+ var UpdateAccessGroupRequestSchema = external_exports.object({
57307
+ name: external_exports.string().min(1).max(100).optional(),
57308
+ slug: external_exports.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9-_]*$/).optional(),
57309
+ description: external_exports.string().max(500).nullable().optional()
57310
+ }).refine((value) => Object.keys(value).length > 0, {
57311
+ message: "At least one field is required"
57312
+ });
57313
+ var AccessGroupResponseSchema = external_exports.object({
57314
+ id: external_exports.string(),
57315
+ org_id: external_exports.string(),
57316
+ name: external_exports.string(),
57317
+ slug: external_exports.string(),
57318
+ description: external_exports.string().nullable(),
57319
+ created_by: external_exports.string().nullable(),
57320
+ created_at: external_exports.string(),
57321
+ updated_at: external_exports.string()
57322
+ });
57323
+ var AccessGroupListResponseSchema = createApiListResponseSchema(AccessGroupResponseSchema);
57324
+ var CreateAccessGroupMemberRequestSchema = external_exports.object({
57325
+ principal_type: AccessGroupMemberPrincipalTypeSchema,
57326
+ principal_id: external_exports.string().min(1)
57327
+ });
57328
+ var AccessGroupMemberResponseSchema = external_exports.object({
57329
+ group_id: external_exports.string(),
57330
+ principal_type: AccessGroupMemberPrincipalTypeSchema,
57331
+ principal_id: external_exports.string(),
57332
+ added_by: external_exports.string().nullable(),
57333
+ created_at: external_exports.string()
57334
+ });
57335
+ var AccessGroupMemberListResponseSchema = createApiListResponseSchema(AccessGroupMemberResponseSchema);
56981
57336
  var AccessCanResponseSchema = external_exports.object({
56982
57337
  allowed: external_exports.boolean(),
56983
- source: external_exports.string()
57338
+ source: external_exports.string(),
57339
+ resource: external_exports.object({
57340
+ type: external_exports.enum(["orgfs", "orgdocs", "envdb"]),
57341
+ id: external_exports.string(),
57342
+ action: external_exports.enum(["read", "write", "admin"]),
57343
+ scope_required: external_exports.boolean(),
57344
+ scope_matched: external_exports.boolean()
57345
+ }).optional()
56984
57346
  });
56985
57347
  var AccessExplainGrantSchema = external_exports.object({
56986
57348
  source: external_exports.string(),
56987
57349
  role: external_exports.string().optional(),
56988
57350
  permissions: external_exports.array(external_exports.string()),
56989
- has_permission: external_exports.boolean()
57351
+ has_permission: external_exports.boolean(),
57352
+ scope_json: AccessBindingScopeSchema.nullable().optional(),
57353
+ scope_match: external_exports.boolean().optional(),
57354
+ scope_reason: external_exports.string().optional()
56990
57355
  });
56991
57356
  var AccessExplainResponseSchema = external_exports.object({
56992
57357
  permission: external_exports.string(),
56993
57358
  result: external_exports.enum(["ALLOWED", "DENIED"]),
56994
57359
  grants: external_exports.array(AccessExplainGrantSchema),
56995
- missing_reason: external_exports.string().optional()
57360
+ missing_reason: external_exports.string().optional(),
57361
+ resource: external_exports.object({
57362
+ type: external_exports.enum(["orgfs", "orgdocs", "envdb"]),
57363
+ id: external_exports.string(),
57364
+ action: external_exports.enum(["read", "write", "admin"]),
57365
+ scope_required: external_exports.boolean(),
57366
+ scope_matched: external_exports.boolean()
57367
+ }).optional()
57368
+ });
57369
+ var AccessMembershipBaseSchema = external_exports.object({
57370
+ org_role: external_exports.enum(["owner", "admin", "member"]).nullable(),
57371
+ project_roles: external_exports.array(external_exports.object({
57372
+ project_id: external_exports.string(),
57373
+ role: external_exports.enum(["owner", "admin", "member"])
57374
+ })),
57375
+ token_scopes: external_exports.array(external_exports.string())
57376
+ });
57377
+ var AccessGroupSummarySchema = external_exports.object({
57378
+ id: external_exports.string(),
57379
+ slug: external_exports.string(),
57380
+ name: external_exports.string()
57381
+ });
57382
+ var AccessResolvedBindingSchema = AccessBindingResponseSchema.extend({
57383
+ role_permissions: external_exports.array(external_exports.string()),
57384
+ matched_via: external_exports.enum(["direct", "group"]).optional(),
57385
+ matched_group_id: external_exports.string().nullable().optional(),
57386
+ matched_group_slug: external_exports.string().nullable().optional()
57387
+ });
57388
+ var AccessEffectiveScopeSummarySchema = external_exports.object({
57389
+ orgfs: external_exports.object({
57390
+ allow_prefixes: external_exports.array(external_exports.string()),
57391
+ read_only_prefixes: external_exports.array(external_exports.string())
57392
+ }),
57393
+ orgdocs: external_exports.object({
57394
+ allow_prefixes: external_exports.array(external_exports.string()),
57395
+ read_only_prefixes: external_exports.array(external_exports.string())
57396
+ }),
57397
+ envdb: external_exports.object({
57398
+ schemas: external_exports.array(external_exports.string()),
57399
+ tables: external_exports.array(external_exports.string())
57400
+ })
57401
+ });
57402
+ var AccessPrincipalMembershipsResponseSchema = external_exports.object({
57403
+ org_id: external_exports.string(),
57404
+ principal_type: AccessPrincipalTypeSchema,
57405
+ principal_id: external_exports.string(),
57406
+ base: AccessMembershipBaseSchema,
57407
+ groups: external_exports.array(AccessGroupSummarySchema),
57408
+ direct_bindings: external_exports.array(AccessBindingResponseSchema),
57409
+ effective_bindings: external_exports.array(AccessResolvedBindingSchema),
57410
+ effective_permissions: external_exports.array(external_exports.string()),
57411
+ effective_scopes: AccessEffectiveScopeSummarySchema
56996
57412
  });
56997
57413
  var AccessYamlRoleSchema = external_exports.object({
56998
57414
  scope: external_exports.enum(["org", "project"]),
56999
57415
  description: external_exports.string().optional(),
57000
57416
  permissions: external_exports.array(external_exports.string()).min(1)
57001
57417
  });
57418
+ var AccessYamlGroupMemberSchema = external_exports.object({
57419
+ type: AccessGroupMemberPrincipalTypeSchema,
57420
+ id: external_exports.string().min(1)
57421
+ });
57422
+ var AccessYamlGroupSchema = external_exports.object({
57423
+ name: external_exports.string().min(1).max(100).optional(),
57424
+ description: external_exports.string().max(500).optional(),
57425
+ members: external_exports.array(AccessYamlGroupMemberSchema).optional()
57426
+ });
57002
57427
  var AccessYamlBindingSchema = external_exports.object({
57003
- scope: external_exports.enum(["org", "project"]),
57004
57428
  project_id: external_exports.string().optional(),
57005
57429
  subject: external_exports.object({
57006
- type: external_exports.enum(["user", "service_principal"]),
57430
+ type: AccessPrincipalTypeSchema,
57007
57431
  id: external_exports.string()
57008
57432
  }),
57009
- roles: external_exports.array(external_exports.string()).min(1)
57433
+ roles: external_exports.array(external_exports.string()).min(1),
57434
+ scope: AccessBindingScopeSchema.optional()
57010
57435
  });
57011
57436
  var AccessYamlSchema = external_exports.object({
57012
- version: external_exports.literal(1),
57437
+ version: external_exports.literal(2),
57013
57438
  access: external_exports.object({
57439
+ groups: external_exports.record(external_exports.string().regex(/^[a-z0-9][a-z0-9-_]*$/), AccessYamlGroupSchema).optional(),
57014
57440
  roles: external_exports.record(external_exports.string(), AccessYamlRoleSchema).optional(),
57015
57441
  bindings: external_exports.array(AccessYamlBindingSchema).optional()
57016
57442
  })
@@ -57561,8 +57987,21 @@ var DbRlsTableSchema = external_exports.object({
57561
57987
  rls_enabled: external_exports.boolean(),
57562
57988
  policies: external_exports.array(DbPolicySchema)
57563
57989
  });
57990
+ var DbRlsDiagnosticsContextSchema = external_exports.object({
57991
+ user_id: external_exports.string().nullable(),
57992
+ principal_type: external_exports.enum(["user", "service_principal"]).nullable(),
57993
+ org_id: external_exports.string().nullable(),
57994
+ project_id: external_exports.string().nullable(),
57995
+ env_name: external_exports.string().nullable(),
57996
+ group_ids: external_exports.array(external_exports.string()),
57997
+ permissions: external_exports.array(external_exports.string())
57998
+ });
57999
+ var DbRlsDiagnosticsSchema = external_exports.object({
58000
+ context: DbRlsDiagnosticsContextSchema
58001
+ });
57564
58002
  var DbRlsResponseSchema = external_exports.object({
57565
- tables: external_exports.array(DbRlsTableSchema)
58003
+ tables: external_exports.array(DbRlsTableSchema),
58004
+ diagnostics: DbRlsDiagnosticsSchema
57566
58005
  });
57567
58006
  var DbSqlRequestSchema = external_exports.object({
57568
58007
  sql: external_exports.string().min(1),
@@ -58363,6 +58802,186 @@ var OrgDocumentQueryResponseSchema = external_exports.object({
58363
58802
  })
58364
58803
  });
58365
58804
 
58805
+ // ../shared/dist/schemas/org-fs-sync.js
58806
+ var OrgFsSyncModeSchema = external_exports.enum(["two_way", "push_only", "pull_only"]);
58807
+ var OrgFsLinkStatusSchema = external_exports.enum(["active", "paused", "revoked"]);
58808
+ var OrgFsDeviceStatusSchema = external_exports.enum(["active", "revoked"]);
58809
+ var OrgFsEventSourceSideSchema = external_exports.enum(["local", "remote", "system"]);
58810
+ var OrgFsOwnerPrincipalTypeSchema = external_exports.enum(["user", "service_principal", "system"]);
58811
+ var OrgFsLinkScopeSchema = external_exports.object({
58812
+ allow_prefixes: external_exports.array(external_exports.string().min(1)),
58813
+ read_only_prefixes: external_exports.array(external_exports.string().min(1)).optional()
58814
+ }).passthrough();
58815
+ var OrgFsDeviceSchema = external_exports.object({
58816
+ id: external_exports.string(),
58817
+ org_id: external_exports.string(),
58818
+ device_name: external_exports.string(),
58819
+ platform: external_exports.string().nullable(),
58820
+ client_version: external_exports.string().nullable(),
58821
+ public_key: external_exports.string(),
58822
+ status: OrgFsDeviceStatusSchema,
58823
+ last_seen_at: external_exports.string().nullable(),
58824
+ created_at: external_exports.string(),
58825
+ updated_at: external_exports.string()
58826
+ });
58827
+ var OrgFsEnrollmentSchema = external_exports.object({
58828
+ token: external_exports.string(),
58829
+ expires_at: external_exports.string(),
58830
+ gateway_url: external_exports.string().url()
58831
+ });
58832
+ var OrgFsEnrollDeviceRequestSchema = external_exports.object({
58833
+ device_name: external_exports.string().min(1),
58834
+ platform: external_exports.string().optional(),
58835
+ client_version: external_exports.string().optional(),
58836
+ public_key: external_exports.string().optional()
58837
+ });
58838
+ var OrgFsEnrollDeviceResponseSchema = external_exports.object({
58839
+ device: OrgFsDeviceSchema,
58840
+ enrollment: OrgFsEnrollmentSchema
58841
+ });
58842
+ var OrgFsLinkSchema = external_exports.object({
58843
+ id: external_exports.string(),
58844
+ org_id: external_exports.string(),
58845
+ device_id: external_exports.string(),
58846
+ owner_principal_type: OrgFsOwnerPrincipalTypeSchema,
58847
+ owner_principal_id: external_exports.string().nullable(),
58848
+ mode: OrgFsSyncModeSchema,
58849
+ status: OrgFsLinkStatusSchema,
58850
+ local_path: external_exports.string(),
58851
+ remote_path: external_exports.string(),
58852
+ scope_json: OrgFsLinkScopeSchema,
58853
+ includes: external_exports.array(external_exports.string()),
58854
+ excludes: external_exports.array(external_exports.string()),
58855
+ last_cursor: external_exports.number().int().nonnegative(),
58856
+ lag_ms: external_exports.number().int().nonnegative().nullable().optional(),
58857
+ backlog: external_exports.number().int().nonnegative().optional(),
58858
+ last_synced_at: external_exports.string().nullable(),
58859
+ last_heartbeat_at: external_exports.string().nullable(),
58860
+ updated_at: external_exports.string(),
58861
+ created_at: external_exports.string()
58862
+ });
58863
+ var OrgFsCreateLinkRequestSchema = external_exports.object({
58864
+ device_id: external_exports.string().min(1),
58865
+ mode: OrgFsSyncModeSchema.default("two_way"),
58866
+ local_path: external_exports.string().min(1),
58867
+ remote_path: external_exports.string().min(1).default("/"),
58868
+ allow_prefixes: external_exports.array(external_exports.string().min(1)).min(1).optional(),
58869
+ includes: external_exports.array(external_exports.string()).optional(),
58870
+ excludes: external_exports.array(external_exports.string()).optional()
58871
+ });
58872
+ var OrgFsLinkGatewayTokenSchema = external_exports.object({
58873
+ token: external_exports.string(),
58874
+ expires_at: external_exports.string(),
58875
+ header: external_exports.string().default("x-eve-internal-token"),
58876
+ link_id: external_exports.string(),
58877
+ mode: OrgFsSyncModeSchema,
58878
+ allow_prefixes: external_exports.array(external_exports.string())
58879
+ });
58880
+ var OrgFsCreateLinkResponseSchema = external_exports.object({
58881
+ link: OrgFsLinkSchema,
58882
+ runtime: external_exports.object({
58883
+ sync_engine: external_exports.literal("syncthing"),
58884
+ profile: external_exports.string(),
58885
+ gateway: OrgFsLinkGatewayTokenSchema
58886
+ })
58887
+ });
58888
+ var OrgFsRotateLinkTokenResponseSchema = external_exports.object({
58889
+ gateway: OrgFsLinkGatewayTokenSchema
58890
+ });
58891
+ var OrgFsListLinksResponseSchema = external_exports.object({
58892
+ data: external_exports.array(OrgFsLinkSchema)
58893
+ });
58894
+ var OrgFsUpdateLinkRequestSchema = external_exports.object({
58895
+ mode: OrgFsSyncModeSchema.optional(),
58896
+ status: OrgFsLinkStatusSchema.optional(),
58897
+ allow_prefixes: external_exports.array(external_exports.string().min(1)).min(1).optional(),
58898
+ includes: external_exports.array(external_exports.string()).optional(),
58899
+ excludes: external_exports.array(external_exports.string()).optional()
58900
+ }).refine((value) => Object.keys(value).length > 0, {
58901
+ message: "At least one field is required"
58902
+ });
58903
+ var OrgFsDeleteLinkResponseSchema = external_exports.object({
58904
+ success: external_exports.boolean()
58905
+ });
58906
+ var OrgFsStatusResponseSchema = external_exports.object({
58907
+ org_id: external_exports.string(),
58908
+ gateway: external_exports.object({
58909
+ status: external_exports.enum(["healthy", "degraded", "offline"]),
58910
+ last_heartbeat_at: external_exports.string().nullable()
58911
+ }),
58912
+ links: external_exports.object({
58913
+ active: external_exports.number().int().nonnegative(),
58914
+ paused: external_exports.number().int().nonnegative(),
58915
+ revoked: external_exports.number().int().nonnegative()
58916
+ }),
58917
+ events: external_exports.object({
58918
+ latest_seq: external_exports.number().int().nonnegative()
58919
+ })
58920
+ });
58921
+ var OrgFsEventSchema = external_exports.object({
58922
+ seq: external_exports.number().int().nonnegative(),
58923
+ event_id: external_exports.string(),
58924
+ org_id: external_exports.string(),
58925
+ link_id: external_exports.string().nullable().optional(),
58926
+ device_id: external_exports.string().nullable().optional(),
58927
+ event_type: external_exports.string(),
58928
+ path: external_exports.string(),
58929
+ content_hash: external_exports.string().nullable().optional(),
58930
+ size_bytes: external_exports.number().int().nonnegative().nullable().optional(),
58931
+ source_side: OrgFsEventSourceSideSchema,
58932
+ metadata: external_exports.record(external_exports.unknown()).optional(),
58933
+ created_at: external_exports.string()
58934
+ });
58935
+ var OrgFsEventListResponseSchema = external_exports.object({
58936
+ data: external_exports.array(OrgFsEventSchema),
58937
+ pagination: external_exports.object({
58938
+ limit: external_exports.number().int().positive(),
58939
+ next_after_seq: external_exports.number().int().nonnegative().nullable()
58940
+ })
58941
+ });
58942
+ var OrgFsConflictSchema = external_exports.object({
58943
+ id: external_exports.string(),
58944
+ org_id: external_exports.string(),
58945
+ link_id: external_exports.string().nullable(),
58946
+ path: external_exports.string(),
58947
+ local_hash: external_exports.string().nullable(),
58948
+ remote_hash: external_exports.string().nullable(),
58949
+ status: external_exports.enum(["open", "resolved"]),
58950
+ resolution: external_exports.enum(["pick_local", "pick_remote", "manual"]).nullable(),
58951
+ resolved_by: external_exports.string().nullable(),
58952
+ resolved_at: external_exports.string().nullable(),
58953
+ created_at: external_exports.string()
58954
+ });
58955
+ var OrgFsListConflictsResponseSchema = external_exports.object({
58956
+ data: external_exports.array(OrgFsConflictSchema)
58957
+ });
58958
+ var OrgFsResolveConflictRequestSchema = external_exports.object({
58959
+ strategy: external_exports.enum(["pick_local", "pick_remote", "manual"]),
58960
+ merged_content: external_exports.string().optional()
58961
+ });
58962
+ var OrgFsResolveConflictResponseSchema = external_exports.object({
58963
+ conflict: OrgFsConflictSchema
58964
+ });
58965
+ var OrgFsInternalIngestEventRequestSchema = external_exports.object({
58966
+ event_id: external_exports.string(),
58967
+ link_id: external_exports.string().optional(),
58968
+ device_id: external_exports.string().optional(),
58969
+ event_type: external_exports.string(),
58970
+ path: external_exports.string(),
58971
+ content_hash: external_exports.string().optional(),
58972
+ size_bytes: external_exports.number().int().nonnegative().optional(),
58973
+ source_side: OrgFsEventSourceSideSchema,
58974
+ metadata: external_exports.record(external_exports.unknown()).optional()
58975
+ });
58976
+ var OrgFsInternalHeartbeatRequestSchema = external_exports.object({
58977
+ cursor: external_exports.number().int().nonnegative().optional(),
58978
+ backlog: external_exports.number().int().nonnegative().optional(),
58979
+ lag_ms: external_exports.number().int().nonnegative().optional()
58980
+ });
58981
+ var OrgFsInternalMetricsRequestSchema = external_exports.object({
58982
+ metrics: external_exports.record(external_exports.unknown())
58983
+ });
58984
+
58366
58985
  // ../shared/dist/schemas/resource.js
58367
58986
  var ResolveResourcesRequestSchema = external_exports.object({
58368
58987
  uris: external_exports.array(external_exports.string().min(1)),
@@ -60767,6 +61386,14 @@ var ALL_PERMISSIONS = [
60767
61386
  // Env DB
60768
61387
  "envdb:read",
60769
61388
  "envdb:write",
61389
+ // Org filesystem data plane
61390
+ "orgfs:read",
61391
+ "orgfs:write",
61392
+ "orgfs:admin",
61393
+ // Org document data plane
61394
+ "orgdocs:read",
61395
+ "orgdocs:write",
61396
+ "orgdocs:admin",
60770
61397
  // Secrets
60771
61398
  "secrets:read",
60772
61399
  "secrets:write",
@@ -61688,11 +62315,11 @@ function formatJobsTable(jobs) {
61688
62315
  const phaseWidth = Math.max(5, ...jobs.map((j) => j.phase.length));
61689
62316
  const titleWidth = Math.min(50, Math.max(5, ...jobs.map((j) => j.title.length)));
61690
62317
  const header = [
61691
- padRight("ID", idWidth),
61692
- padRight("P", 2),
61693
- padRight("Phase", phaseWidth),
61694
- padRight("Type", 8),
61695
- padRight("Title", titleWidth),
62318
+ padRight2("ID", idWidth),
62319
+ padRight2("P", 2),
62320
+ padRight2("Phase", phaseWidth),
62321
+ padRight2("Type", 8),
62322
+ padRight2("Title", titleWidth),
61696
62323
  "Assignee"
61697
62324
  ].join(" ");
61698
62325
  console.log(header);
@@ -61700,11 +62327,11 @@ function formatJobsTable(jobs) {
61700
62327
  for (const job of jobs) {
61701
62328
  const title = job.title.length > titleWidth ? job.title.slice(0, titleWidth - 3) + "..." : job.title;
61702
62329
  const row = [
61703
- padRight(job.id, idWidth),
61704
- padRight(`P${job.priority}`, 2),
61705
- padRight(job.phase, phaseWidth),
61706
- padRight(job.issue_type, 8),
61707
- padRight(title, titleWidth),
62330
+ padRight2(job.id, idWidth),
62331
+ padRight2(`P${job.priority}`, 2),
62332
+ padRight2(job.phase, phaseWidth),
62333
+ padRight2(job.issue_type, 8),
62334
+ padRight2(title, titleWidth),
61708
62335
  job.assignee ?? "-"
61709
62336
  ].join(" ");
61710
62337
  console.log(row);
@@ -63344,7 +63971,7 @@ function formatDuration(startStr, endStr) {
63344
63971
  return "unknown";
63345
63972
  }
63346
63973
  }
63347
- function padRight(str, width) {
63974
+ function padRight2(str, width) {
63348
63975
  if (str.length >= width) return str;
63349
63976
  return str + " ".repeat(width - str.length);
63350
63977
  }
@@ -63938,11 +64565,11 @@ Permissions (${data.permissions.length}):`);
63938
64565
  }
63939
64566
  if (status) {
63940
64567
  if (status.completed) {
63941
- throw new Error("Bootstrap already completed. Use eve auth login instead.");
64568
+ console.log("Bootstrap already completed. Attempting server-side recovery flow.");
63942
64569
  }
63943
- if (!status.requires_token && status.window_open) {
64570
+ if (!status.completed && !status.requires_token && status.window_open) {
63944
64571
  console.log(`Bootstrap window open (${status.mode} mode). Token not required.`);
63945
- } else if (status.requires_token && !token) {
64572
+ } else if (!status.completed && status.requires_token && !token) {
63946
64573
  throw new Error("Bootstrap token required. Use --token <token> or set EVE_BOOTSTRAP_TOKEN");
63947
64574
  }
63948
64575
  } else if (!token) {
@@ -64977,10 +65604,10 @@ function renderDiscoveredModels(response) {
64977
65604
  ${response.models.length} model(s) discovered`);
64978
65605
  }
64979
65606
  function formatRow2(columns, widths) {
64980
- const cells = columns.map((value, idx) => ` ${padRight2(value, widths[idx])} `);
65607
+ const cells = columns.map((value, idx) => ` ${padRight3(value, widths[idx])} `);
64981
65608
  return `|${cells.join("|")}|`;
64982
65609
  }
64983
- function padRight2(value, width) {
65610
+ function padRight3(value, width) {
64984
65611
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
64985
65612
  }
64986
65613
 
@@ -65195,7 +65822,7 @@ async function handleSystem(subcommand, positionals, flags, context2) {
65195
65822
  const json = Boolean(flags.json);
65196
65823
  switch (subcommand) {
65197
65824
  case "status":
65198
- return handleStatus(context2, json);
65825
+ return handleStatus2(context2, json);
65199
65826
  case "health":
65200
65827
  return handleHealth(context2, json);
65201
65828
  case "jobs":
@@ -65218,7 +65845,7 @@ async function handleSystem(subcommand, positionals, flags, context2) {
65218
65845
  throw new Error("Usage: eve system <status|health|jobs|envs|logs|pods|events|config|settings|orchestrator>");
65219
65846
  }
65220
65847
  }
65221
- async function handleStatus(context2, json) {
65848
+ async function handleStatus2(context2, json) {
65222
65849
  try {
65223
65850
  const status = await requestJson(context2, "/system/status");
65224
65851
  if (json) {
@@ -65584,11 +66211,11 @@ function formatJobsTable2(jobs) {
65584
66211
  const phaseWidth = Math.max(5, ...jobs.map((j) => j.phase.length));
65585
66212
  const titleWidth = Math.min(40, Math.max(5, ...jobs.map((j) => j.title.length)));
65586
66213
  const header = [
65587
- padRight3("Job ID", idWidth),
65588
- padRight3("Project", projectWidth),
65589
- padRight3("Phase", phaseWidth),
65590
- padRight3("P", 2),
65591
- padRight3("Title", titleWidth),
66214
+ padRight4("Job ID", idWidth),
66215
+ padRight4("Project", projectWidth),
66216
+ padRight4("Phase", phaseWidth),
66217
+ padRight4("P", 2),
66218
+ padRight4("Title", titleWidth),
65592
66219
  "Assignee"
65593
66220
  ].join(" ");
65594
66221
  console.log(header);
@@ -65596,11 +66223,11 @@ function formatJobsTable2(jobs) {
65596
66223
  for (const job of jobs) {
65597
66224
  const title = job.title.length > titleWidth ? job.title.slice(0, titleWidth - 3) + "..." : job.title;
65598
66225
  const row = [
65599
- padRight3(job.id, idWidth),
65600
- padRight3(job.project_id, projectWidth),
65601
- padRight3(job.phase, phaseWidth),
65602
- padRight3(`P${job.priority}`, 2),
65603
- padRight3(title, titleWidth),
66226
+ padRight4(job.id, idWidth),
66227
+ padRight4(job.project_id, projectWidth),
66228
+ padRight4(job.phase, phaseWidth),
66229
+ padRight4(`P${job.priority}`, 2),
66230
+ padRight4(title, titleWidth),
65604
66231
  job.assignee ?? "-"
65605
66232
  ].join(" ");
65606
66233
  console.log(row);
@@ -65619,21 +66246,21 @@ function formatEnvsTable(envs) {
65619
66246
  const namespaceWidth = Math.max(9, ...envs.map((e) => (e.namespace ?? "-").length));
65620
66247
  const releaseWidth = Math.max(7, ...envs.map((e) => (e.current_release ?? "-").length));
65621
66248
  const header = [
65622
- padRight3("Project", projectWidth),
65623
- padRight3("Environment", nameWidth),
65624
- padRight3("Type", typeWidth),
65625
- padRight3("Namespace", namespaceWidth),
65626
- padRight3("Release", releaseWidth)
66249
+ padRight4("Project", projectWidth),
66250
+ padRight4("Environment", nameWidth),
66251
+ padRight4("Type", typeWidth),
66252
+ padRight4("Namespace", namespaceWidth),
66253
+ padRight4("Release", releaseWidth)
65627
66254
  ].join(" ");
65628
66255
  console.log(header);
65629
66256
  console.log("-".repeat(header.length));
65630
66257
  for (const env of envs) {
65631
66258
  const row = [
65632
- padRight3(env.project_id, projectWidth),
65633
- padRight3(env.name, nameWidth),
65634
- padRight3(env.type, typeWidth),
65635
- padRight3(env.namespace ?? "-", namespaceWidth),
65636
- padRight3(env.current_release ?? "-", releaseWidth)
66259
+ padRight4(env.project_id, projectWidth),
66260
+ padRight4(env.name, nameWidth),
66261
+ padRight4(env.type, typeWidth),
66262
+ padRight4(env.namespace ?? "-", namespaceWidth),
66263
+ padRight4(env.current_release ?? "-", releaseWidth)
65637
66264
  ].join(" ");
65638
66265
  console.log(row);
65639
66266
  }
@@ -65652,12 +66279,12 @@ function formatPodsTable(pods) {
65652
66279
  const restartsWidth = Math.max(8, ...pods.map((p) => String(p.restarts).length));
65653
66280
  const ageWidth = Math.max(3, ...pods.map((p) => p.age.length));
65654
66281
  const header = [
65655
- padRight3("Name", nameWidth),
65656
- padRight3("Namespace", nsWidth),
65657
- padRight3("Phase", phaseWidth),
65658
- padRight3("Ready", readyWidth),
65659
- padRight3("Restarts", restartsWidth),
65660
- padRight3("Age", ageWidth),
66282
+ padRight4("Name", nameWidth),
66283
+ padRight4("Namespace", nsWidth),
66284
+ padRight4("Phase", phaseWidth),
66285
+ padRight4("Ready", readyWidth),
66286
+ padRight4("Restarts", restartsWidth),
66287
+ padRight4("Age", ageWidth),
65661
66288
  "Component",
65662
66289
  "Org",
65663
66290
  "Project",
@@ -65667,20 +66294,20 @@ function formatPodsTable(pods) {
65667
66294
  console.log("-".repeat(header.length));
65668
66295
  for (const pod of pods) {
65669
66296
  console.log([
65670
- padRight3(pod.name, nameWidth),
65671
- padRight3(pod.namespace, nsWidth),
65672
- padRight3(pod.phase, phaseWidth),
65673
- padRight3(pod.ready ? "yes" : "no", readyWidth),
65674
- padRight3(String(pod.restarts), restartsWidth),
65675
- padRight3(pod.age, ageWidth),
65676
- padRight3(pod.component ?? "-", 10),
65677
- padRight3(pod.orgId ?? "-", 10),
65678
- padRight3(pod.projectId ?? "-", 10),
65679
- padRight3(pod.env ?? "-", 10)
66297
+ padRight4(pod.name, nameWidth),
66298
+ padRight4(pod.namespace, nsWidth),
66299
+ padRight4(pod.phase, phaseWidth),
66300
+ padRight4(pod.ready ? "yes" : "no", readyWidth),
66301
+ padRight4(String(pod.restarts), restartsWidth),
66302
+ padRight4(pod.age, ageWidth),
66303
+ padRight4(pod.component ?? "-", 10),
66304
+ padRight4(pod.orgId ?? "-", 10),
66305
+ padRight4(pod.projectId ?? "-", 10),
66306
+ padRight4(pod.env ?? "-", 10)
65680
66307
  ].join(" "));
65681
66308
  }
65682
66309
  }
65683
- function padRight3(str, width) {
66310
+ function padRight4(str, width) {
65684
66311
  if (str.length >= width) return str;
65685
66312
  return str + " ".repeat(width - str.length);
65686
66313
  }
@@ -65897,6 +66524,8 @@ function stripFilePath(filePath) {
65897
66524
  }
65898
66525
 
65899
66526
  // src/commands/env.ts
66527
+ var import_node_fs7 = require("node:fs");
66528
+ var import_node_path7 = require("node:path");
65900
66529
  var readline2 = __toESM(require("node:readline/promises"));
65901
66530
  async function handleEnv(subcommand, positionals, flags, context2) {
65902
66531
  const json = Boolean(flags.json);
@@ -66048,12 +66677,47 @@ async function handleDeploy(positionals, flags, context2, json) {
66048
66677
  if (!json) {
66049
66678
  console.log(`Deploying commit ${gitSha.substring(0, 8)} to ${envName}...`);
66050
66679
  }
66051
- const manifest = await requestJson(
66052
- context2,
66053
- `/projects/${projectId}/manifest`
66054
- );
66055
- if (!json) {
66056
- console.log(`Using manifest ${manifest.manifest_hash.substring(0, 8)}...`);
66680
+ let manifestHash;
66681
+ if (repoDir) {
66682
+ const manifestPath = (0, import_node_path7.join)(repoDir, ".eve", "manifest.yaml");
66683
+ if ((0, import_node_fs7.existsSync)(manifestPath)) {
66684
+ const manifestYaml = (0, import_node_fs7.readFileSync)(manifestPath, "utf-8");
66685
+ const branch = getGitBranch(repoDir);
66686
+ const syncResponse = await requestJson(
66687
+ context2,
66688
+ `/projects/${projectId}/manifest`,
66689
+ {
66690
+ method: "POST",
66691
+ body: {
66692
+ yaml: manifestYaml,
66693
+ git_sha: gitSha,
66694
+ branch
66695
+ }
66696
+ }
66697
+ );
66698
+ manifestHash = syncResponse.manifest_hash;
66699
+ if (!json) {
66700
+ console.log(`Synced manifest ${manifestHash.substring(0, 8)}...`);
66701
+ }
66702
+ } else {
66703
+ const manifest = await requestJson(
66704
+ context2,
66705
+ `/projects/${projectId}/manifest`
66706
+ );
66707
+ manifestHash = manifest.manifest_hash;
66708
+ if (!json) {
66709
+ console.log(`Using manifest ${manifestHash.substring(0, 8)}...`);
66710
+ }
66711
+ }
66712
+ } else {
66713
+ const manifest = await requestJson(
66714
+ context2,
66715
+ `/projects/${projectId}/manifest`
66716
+ );
66717
+ manifestHash = manifest.manifest_hash;
66718
+ if (!json) {
66719
+ console.log(`Using manifest ${manifestHash.substring(0, 8)}...`);
66720
+ }
66057
66721
  }
66058
66722
  const direct = Boolean(flags.direct);
66059
66723
  let inputs;
@@ -66074,7 +66738,7 @@ Example: --inputs '{"release_id":"rel_xxx","smoke_test":false}'`
66074
66738
  const imageTag = getStringFlag(flags, ["image-tag", "image_tag", "imageTag"]);
66075
66739
  const body = {
66076
66740
  git_sha: gitSha,
66077
- manifest_hash: manifest.manifest_hash
66741
+ manifest_hash: manifestHash
66078
66742
  };
66079
66743
  if (direct) {
66080
66744
  body.direct = true;
@@ -66351,9 +67015,9 @@ function formatEnvironmentsTable(environments) {
66351
67015
  const typeWidth = Math.max(4, ...environments.map((e) => e.type.length));
66352
67016
  const namespaceWidth = Math.max(9, ...environments.map((e) => e.namespace?.length ?? 0));
66353
67017
  const header = [
66354
- padRight4("Name", nameWidth),
66355
- padRight4("Type", typeWidth),
66356
- padRight4("Namespace", namespaceWidth),
67018
+ padRight5("Name", nameWidth),
67019
+ padRight5("Type", typeWidth),
67020
+ padRight5("Namespace", namespaceWidth),
66357
67021
  "Current Release"
66358
67022
  ].join(" ");
66359
67023
  console.log(header);
@@ -66361,9 +67025,9 @@ function formatEnvironmentsTable(environments) {
66361
67025
  for (const env of environments) {
66362
67026
  const releaseDisplay = env.current_release_id ? env.current_release_id.substring(0, 12) + "..." : "-";
66363
67027
  const row = [
66364
- padRight4(env.name, nameWidth),
66365
- padRight4(env.type, typeWidth),
66366
- padRight4(env.namespace || "-", namespaceWidth),
67028
+ padRight5(env.name, nameWidth),
67029
+ padRight5(env.type, typeWidth),
67030
+ padRight5(env.namespace || "-", namespaceWidth),
66367
67031
  releaseDisplay
66368
67032
  ].join(" ");
66369
67033
  console.log(row);
@@ -66455,21 +67119,21 @@ function formatEnvServices(report, services) {
66455
67119
  const restartsWidth = Math.max(8, ...services.map((s) => String(s.restarts).length));
66456
67120
  const phasesWidth = Math.max(6, ...services.map((s) => s.phases.join(", ").length));
66457
67121
  const header = [
66458
- padRight4("Service", nameWidth),
66459
- padRight4("Pods", totalWidth),
66460
- padRight4("Ready", readyWidth),
66461
- padRight4("Restarts", restartsWidth),
66462
- padRight4("Phases", phasesWidth)
67122
+ padRight5("Service", nameWidth),
67123
+ padRight5("Pods", totalWidth),
67124
+ padRight5("Ready", readyWidth),
67125
+ padRight5("Restarts", restartsWidth),
67126
+ padRight5("Phases", phasesWidth)
66463
67127
  ].join(" ");
66464
67128
  console.log(header);
66465
67129
  console.log("-".repeat(header.length));
66466
67130
  for (const service of services) {
66467
67131
  console.log([
66468
- padRight4(service.name, nameWidth),
66469
- padRight4(String(service.pods_total), totalWidth),
66470
- padRight4(String(service.pods_ready), readyWidth),
66471
- padRight4(String(service.restarts), restartsWidth),
66472
- padRight4(service.phases.join(", "), phasesWidth)
67132
+ padRight5(service.name, nameWidth),
67133
+ padRight5(String(service.pods_total), totalWidth),
67134
+ padRight5(String(service.pods_ready), readyWidth),
67135
+ padRight5(String(service.restarts), restartsWidth),
67136
+ padRight5(service.phases.join(", "), phasesWidth)
66473
67137
  ].join(" "));
66474
67138
  }
66475
67139
  }
@@ -66479,8 +67143,8 @@ function formatEnvServices(report, services) {
66479
67143
  const nameWidth = Math.max(4, ...report.deployments.map((d) => d.name.length));
66480
67144
  const readyWidth = Math.max(5, ...report.deployments.map((d) => `${d.available_replicas}/${d.desired_replicas}`.length));
66481
67145
  const header = [
66482
- padRight4("Name", nameWidth),
66483
- padRight4("Ready", readyWidth),
67146
+ padRight5("Name", nameWidth),
67147
+ padRight5("Ready", readyWidth),
66484
67148
  "Status"
66485
67149
  ].join(" ");
66486
67150
  console.log(header);
@@ -66489,8 +67153,8 @@ function formatEnvServices(report, services) {
66489
67153
  const readiness = `${deployment.available_replicas}/${deployment.desired_replicas}`;
66490
67154
  const status = deployment.ready ? "ready" : "not-ready";
66491
67155
  console.log([
66492
- padRight4(deployment.name, nameWidth),
66493
- padRight4(readiness, readyWidth),
67156
+ padRight5(deployment.name, nameWidth),
67157
+ padRight5(readiness, readyWidth),
66494
67158
  status
66495
67159
  ].join(" "));
66496
67160
  }
@@ -66519,8 +67183,8 @@ function formatEnvDiagnose(report) {
66519
67183
  const nameWidth = Math.max(4, ...report.deployments.map((d) => d.name.length));
66520
67184
  const readyWidth = Math.max(5, ...report.deployments.map((d) => `${d.available_replicas}/${d.desired_replicas}`.length));
66521
67185
  const header = [
66522
- padRight4("Name", nameWidth),
66523
- padRight4("Ready", readyWidth),
67186
+ padRight5("Name", nameWidth),
67187
+ padRight5("Ready", readyWidth),
66524
67188
  "Status"
66525
67189
  ].join(" ");
66526
67190
  console.log(header);
@@ -66529,8 +67193,8 @@ function formatEnvDiagnose(report) {
66529
67193
  const readiness = `${deployment.available_replicas}/${deployment.desired_replicas}`;
66530
67194
  const status = deployment.ready ? "ready" : "not-ready";
66531
67195
  console.log([
66532
- padRight4(deployment.name, nameWidth),
66533
- padRight4(readiness, readyWidth),
67196
+ padRight5(deployment.name, nameWidth),
67197
+ padRight5(readiness, readyWidth),
66534
67198
  status
66535
67199
  ].join(" "));
66536
67200
  }
@@ -66542,9 +67206,9 @@ function formatEnvDiagnose(report) {
66542
67206
  const phaseWidth = Math.max(5, ...report.pods.map((p) => p.phase.length));
66543
67207
  const restartsWidth = Math.max(8, ...report.pods.map((p) => String(p.restarts).length));
66544
67208
  const header = [
66545
- padRight4("Name", nameWidth),
66546
- padRight4("Phase", phaseWidth),
66547
- padRight4("Restarts", restartsWidth),
67209
+ padRight5("Name", nameWidth),
67210
+ padRight5("Phase", phaseWidth),
67211
+ padRight5("Restarts", restartsWidth),
66548
67212
  "Ready",
66549
67213
  "Age"
66550
67214
  ].join(" ");
@@ -66552,9 +67216,9 @@ function formatEnvDiagnose(report) {
66552
67216
  console.log("-".repeat(header.length));
66553
67217
  for (const pod of report.pods) {
66554
67218
  console.log([
66555
- padRight4(pod.name, nameWidth),
66556
- padRight4(pod.phase, phaseWidth),
66557
- padRight4(String(pod.restarts), restartsWidth),
67219
+ padRight5(pod.name, nameWidth),
67220
+ padRight5(pod.phase, phaseWidth),
67221
+ padRight5(String(pod.restarts), restartsWidth),
66558
67222
  pod.ready ? "yes" : "no",
66559
67223
  pod.age
66560
67224
  ].join(" "));
@@ -66654,7 +67318,7 @@ function formatDate2(dateStr) {
66654
67318
  return dateStr;
66655
67319
  }
66656
67320
  }
66657
- function padRight4(str, width) {
67321
+ function padRight5(str, width) {
66658
67322
  if (str.length >= width) return str;
66659
67323
  return str + " ".repeat(width - str.length);
66660
67324
  }
@@ -67552,8 +68216,8 @@ function formatLifecycleMeta2(phase, meta) {
67552
68216
  }
67553
68217
 
67554
68218
  // src/commands/api.ts
67555
- var import_node_fs7 = require("node:fs");
67556
- var import_node_path7 = require("node:path");
68219
+ var import_node_fs8 = require("node:fs");
68220
+ var import_node_path8 = require("node:path");
67557
68221
  var import_node_child_process6 = require("node:child_process");
67558
68222
  var import_node_os4 = require("node:os");
67559
68223
  async function handleApi(subcommand, positionals, flags, context2) {
@@ -67672,7 +68336,7 @@ async function handleExamples(positionals, flags, context2, jsonOutput) {
67672
68336
  async function handleGenerate(positionals, flags, jsonOutput) {
67673
68337
  const outDir = getStringFlag(flags, ["out"]) ?? positionals[0];
67674
68338
  const repoRoot = resolveRepoRoot();
67675
- const outputDir = outDir ? (0, import_node_path7.resolve)(repoRoot, outDir) : (0, import_node_path7.resolve)(repoRoot, "docs/system");
68339
+ const outputDir = outDir ? (0, import_node_path8.resolve)(repoRoot, outDir) : (0, import_node_path8.resolve)(repoRoot, "docs/system");
67676
68340
  runOpenApiExport(repoRoot, outputDir);
67677
68341
  outputJson({ ok: true, output_dir: outputDir }, jsonOutput, `OpenAPI exported to ${outputDir}`);
67678
68342
  }
@@ -67680,13 +68344,13 @@ async function handleDiff(positionals, flags, jsonOutput) {
67680
68344
  const exitCode = Boolean(flags["exit-code"]);
67681
68345
  const repoRoot = resolveRepoRoot();
67682
68346
  const expectedDir = getStringFlag(flags, ["out"]) ?? positionals[0];
67683
- const targetDir = expectedDir ? (0, import_node_path7.resolve)(repoRoot, expectedDir) : (0, import_node_path7.resolve)(repoRoot, "docs/system");
67684
- const tempDir = (0, import_node_fs7.mkdtempSync)((0, import_node_path7.join)((0, import_node_os4.tmpdir)(), "eve-openapi-"));
68347
+ const targetDir = expectedDir ? (0, import_node_path8.resolve)(repoRoot, expectedDir) : (0, import_node_path8.resolve)(repoRoot, "docs/system");
68348
+ const tempDir = (0, import_node_fs8.mkdtempSync)((0, import_node_path8.join)((0, import_node_os4.tmpdir)(), "eve-openapi-"));
67685
68349
  try {
67686
68350
  runOpenApiExport(repoRoot, tempDir);
67687
- const actualPath = (0, import_node_path7.resolve)(tempDir, "openapi.yaml");
67688
- const expectedPath = (0, import_node_path7.resolve)(targetDir, "openapi.yaml");
67689
- if (!(0, import_node_fs7.existsSync)(expectedPath)) {
68351
+ const actualPath = (0, import_node_path8.resolve)(tempDir, "openapi.yaml");
68352
+ const expectedPath = (0, import_node_path8.resolve)(targetDir, "openapi.yaml");
68353
+ if (!(0, import_node_fs8.existsSync)(expectedPath)) {
67690
68354
  throw new Error(`Missing expected OpenAPI spec at ${expectedPath}`);
67691
68355
  }
67692
68356
  const diff = (0, import_node_child_process6.spawnSync)("diff", ["-u", expectedPath, actualPath], { stdio: "inherit" });
@@ -67696,7 +68360,7 @@ async function handleDiff(positionals, flags, jsonOutput) {
67696
68360
  }
67697
68361
  outputJson({ ok: !hasDiff }, jsonOutput, hasDiff ? "OpenAPI spec drift detected" : "OpenAPI spec matches");
67698
68362
  } finally {
67699
- (0, import_node_fs7.rmSync)(tempDir, { recursive: true, force: true });
68363
+ (0, import_node_fs8.rmSync)(tempDir, { recursive: true, force: true });
67700
68364
  }
67701
68365
  }
67702
68366
  async function handleCall(positionals, flags, context2) {
@@ -67985,10 +68649,10 @@ function stripTrailingSlash(url) {
67985
68649
  function resolveRepoRoot() {
67986
68650
  let current = process.cwd();
67987
68651
  while (true) {
67988
- if ((0, import_node_fs7.existsSync)((0, import_node_path7.resolve)(current, "pnpm-workspace.yaml"))) {
68652
+ if ((0, import_node_fs8.existsSync)((0, import_node_path8.resolve)(current, "pnpm-workspace.yaml"))) {
67989
68653
  return current;
67990
68654
  }
67991
- const parent = (0, import_node_path7.dirname)(current);
68655
+ const parent = (0, import_node_path8.dirname)(current);
67992
68656
  if (parent === current) {
67993
68657
  break;
67994
68658
  }
@@ -67997,9 +68661,9 @@ function resolveRepoRoot() {
67997
68661
  throw new Error("Unable to locate repo root (pnpm-workspace.yaml not found).");
67998
68662
  }
67999
68663
  function runOpenApiExport(repoRoot, outputDir) {
68000
- const scriptPath = (0, import_node_path7.resolve)(repoRoot, "apps/api/dist/scripts/export-openapi.js");
68001
- if (!(0, import_node_fs7.existsSync)(scriptPath)) {
68002
- const build = (0, import_node_child_process6.spawnSync)("pnpm", ["-C", (0, import_node_path7.resolve)(repoRoot, "apps/api"), "build"], { stdio: "inherit" });
68664
+ const scriptPath = (0, import_node_path8.resolve)(repoRoot, "apps/api/dist/scripts/export-openapi.js");
68665
+ if (!(0, import_node_fs8.existsSync)(scriptPath)) {
68666
+ const build = (0, import_node_child_process6.spawnSync)("pnpm", ["-C", (0, import_node_path8.resolve)(repoRoot, "apps/api"), "build"], { stdio: "inherit" });
68003
68667
  if (build.status !== 0) {
68004
68668
  throw new Error("Failed to build API before exporting OpenAPI spec");
68005
68669
  }
@@ -68035,14 +68699,14 @@ function resolveJsonInput(value) {
68035
68699
  }
68036
68700
  function resolveTextInput(value) {
68037
68701
  if (value.startsWith("@")) {
68038
- const filePath = (0, import_node_path7.resolve)(value.slice(1));
68039
- return (0, import_node_fs7.readFileSync)(filePath, "utf-8");
68702
+ const filePath = (0, import_node_path8.resolve)(value.slice(1));
68703
+ return (0, import_node_fs8.readFileSync)(filePath, "utf-8");
68040
68704
  }
68041
68705
  if (value.trim().startsWith("{") || value.trim().startsWith("[")) {
68042
68706
  return value;
68043
68707
  }
68044
- if ((0, import_node_fs7.existsSync)(value)) {
68045
- return (0, import_node_fs7.readFileSync)((0, import_node_path7.resolve)(value), "utf-8");
68708
+ if ((0, import_node_fs8.existsSync)(value)) {
68709
+ return (0, import_node_fs8.readFileSync)((0, import_node_path8.resolve)(value), "utf-8");
68046
68710
  }
68047
68711
  return value;
68048
68712
  }
@@ -68085,8 +68749,8 @@ function buildCurlCommand(options) {
68085
68749
  }
68086
68750
 
68087
68751
  // src/commands/db.ts
68088
- var import_node_fs8 = require("node:fs");
68089
- var import_node_path8 = require("node:path");
68752
+ var import_node_fs9 = require("node:fs");
68753
+ var import_node_path9 = require("node:path");
68090
68754
  var MIGRATION_REGEX = /^(\d{14})_([a-z0-9_]+)\.sql$/;
68091
68755
  async function handleDb(subcommand, positionals, flags, context2) {
68092
68756
  const jsonOutput = Boolean(flags.json);
@@ -68104,7 +68768,7 @@ async function handleDb(subcommand, positionals, flags, context2) {
68104
68768
  case "new":
68105
68769
  return handleNew(positionals, flags);
68106
68770
  case "status":
68107
- return handleStatus2(positionals, flags, context2, jsonOutput);
68771
+ return handleStatus3(positionals, flags, context2, jsonOutput);
68108
68772
  case "rotate-credentials":
68109
68773
  return handleRotateCredentials(positionals, flags, context2, jsonOutput);
68110
68774
  case "scale":
@@ -68113,7 +68777,7 @@ async function handleDb(subcommand, positionals, flags, context2) {
68113
68777
  return handleDestroy(positionals, flags, context2, jsonOutput);
68114
68778
  default:
68115
68779
  throw new Error(
68116
- "Usage: eve db <command> [options]\n\nCommands:\n schema --env <name> Show DB schema info\n rls --env <name> Show RLS policies and tables\n sql --env <name> --sql <stmt> Run parameterized SQL\n migrate --env <name> [--path <dir>] Apply pending migrations\n migrations --env <name> List applied migrations\n new <description> Create new migration file\n status --env <name> Show managed DB status\n rotate-credentials --env <name> Rotate managed DB credentials\n scale --env <name> --class <cls> Scale managed DB class\n destroy --env <name> --force Destroy managed DB"
68780
+ "Usage: eve db <command> [options]\n\nCommands:\n schema --env <name> Show DB schema info\n rls --env <name> Show RLS policies and tables\n rls init --with-groups Scaffold group-aware RLS helper SQL\n sql --env <name> --sql <stmt> Run parameterized SQL\n migrate --env <name> [--path <dir>] Apply pending migrations\n migrations --env <name> List applied migrations\n new <description> Create new migration file\n status --env <name> Show managed DB status\n rotate-credentials --env <name> Rotate managed DB credentials\n scale --env <name> --class <cls> Scale managed DB class\n destroy --env <name> --force Destroy managed DB"
68117
68781
  );
68118
68782
  }
68119
68783
  }
@@ -68123,9 +68787,109 @@ async function handleSchema(positionals, flags, context2, jsonOutput) {
68123
68787
  outputJson(response, jsonOutput);
68124
68788
  }
68125
68789
  async function handleRls(positionals, flags, context2, jsonOutput) {
68790
+ if (positionals[0] === "init") {
68791
+ return handleRlsInit(flags, jsonOutput);
68792
+ }
68126
68793
  const { projectId, envName } = resolveProjectEnv(positionals, flags, context2);
68127
68794
  const response = await requestJson(context2, `/projects/${projectId}/envs/${envName}/db/rls`);
68128
- outputJson(response, jsonOutput);
68795
+ if (jsonOutput) {
68796
+ outputJson(response, true);
68797
+ return;
68798
+ }
68799
+ printRlsResponse(response);
68800
+ }
68801
+ function handleRlsInit(flags, jsonOutput) {
68802
+ const withGroups = toBoolean(flags["with-groups"]) ?? false;
68803
+ const force = toBoolean(flags.force) ?? false;
68804
+ if (!withGroups) {
68805
+ throw new Error("Usage: eve db rls init --with-groups [--out <path>] [--force]");
68806
+ }
68807
+ const outPath = getStringFlag(flags, ["out"]) ?? "db/rls/helpers.sql";
68808
+ const fullPath = (0, import_node_path9.resolve)(outPath);
68809
+ if ((0, import_node_fs9.existsSync)(fullPath) && !force) {
68810
+ throw new Error(`RLS helper file already exists: ${fullPath}
68811
+ Use --force to overwrite.`);
68812
+ }
68813
+ (0, import_node_fs9.mkdirSync)((0, import_node_path9.dirname)(fullPath), { recursive: true });
68814
+ (0, import_node_fs9.writeFileSync)(fullPath, renderRlsHelperTemplate());
68815
+ const payload = {
68816
+ path: fullPath,
68817
+ with_groups: true
68818
+ };
68819
+ outputJson(payload, jsonOutput, `Created group-aware RLS helpers at ${fullPath}`);
68820
+ }
68821
+ function renderRlsHelperTemplate() {
68822
+ return `-- Eve RLS helpers generated by "eve db rls init --with-groups"
68823
+ -- Usage:
68824
+ -- 1. Apply this SQL in your target environment DB.
68825
+ -- 2. Reference app.current_user_id()/app.current_group_ids()/app.has_group() in policies.
68826
+ --
68827
+ -- Example:
68828
+ -- CREATE POLICY notes_group_read
68829
+ -- ON notes
68830
+ -- FOR SELECT
68831
+ -- USING (group_id = ANY(app.current_group_ids()));
68832
+
68833
+ CREATE SCHEMA IF NOT EXISTS app;
68834
+
68835
+ CREATE OR REPLACE FUNCTION app.current_user_id()
68836
+ RETURNS text
68837
+ LANGUAGE sql
68838
+ STABLE
68839
+ AS $$
68840
+ SELECT NULLIF(current_setting('app.user_id', true), '');
68841
+ $$;
68842
+
68843
+ CREATE OR REPLACE FUNCTION app.current_group_ids()
68844
+ RETURNS text[]
68845
+ LANGUAGE sql
68846
+ STABLE
68847
+ AS $$
68848
+ WITH raw AS (
68849
+ SELECT NULLIF(current_setting('app.group_ids', true), '') AS value
68850
+ )
68851
+ SELECT COALESCE(
68852
+ ARRAY(
68853
+ SELECT jsonb_array_elements_text(COALESCE(raw.value::jsonb, '[]'::jsonb))
68854
+ FROM raw
68855
+ ),
68856
+ ARRAY[]::text[]
68857
+ );
68858
+ $$;
68859
+
68860
+ CREATE OR REPLACE FUNCTION app.has_group(group_id text)
68861
+ RETURNS boolean
68862
+ LANGUAGE sql
68863
+ STABLE
68864
+ AS $$
68865
+ SELECT group_id = ANY(app.current_group_ids());
68866
+ $$;
68867
+ `;
68868
+ }
68869
+ function printRlsResponse(response) {
68870
+ const diagnostics = response.diagnostics?.context;
68871
+ if (diagnostics) {
68872
+ const groups = diagnostics.group_ids ?? [];
68873
+ const permissions = diagnostics.permissions ?? [];
68874
+ console.log("Context:");
68875
+ console.log(` Principal: ${diagnostics.principal_type ?? "unknown"} ${diagnostics.user_id ?? "(none)"}`);
68876
+ console.log(` Org: ${diagnostics.org_id ?? "(none)"}`);
68877
+ console.log(` Project: ${diagnostics.project_id ?? "(none)"}`);
68878
+ console.log(` Env: ${diagnostics.env_name ?? "(none)"}`);
68879
+ console.log(` Groups (${groups.length}): ${groups.length > 0 ? groups.join(", ") : "(none)"}`);
68880
+ console.log(` Permissions (${permissions.length}): ${permissions.length > 0 ? permissions.join(", ") : "(none)"}`);
68881
+ console.log("");
68882
+ }
68883
+ const tables = response.tables ?? [];
68884
+ if (tables.length === 0) {
68885
+ console.log("No user tables/views found.");
68886
+ return;
68887
+ }
68888
+ console.log(`RLS tables (${tables.length}):`);
68889
+ for (const table of tables) {
68890
+ const state = table.rls_enabled ? "enabled" : "disabled";
68891
+ console.log(` - ${table.schema}.${table.name} (${state}, ${table.policies.length} policies)`);
68892
+ }
68129
68893
  }
68130
68894
  async function handleSql(positionals, flags, context2, jsonOutput) {
68131
68895
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
@@ -68137,7 +68901,7 @@ async function handleSql(positionals, flags, context2, jsonOutput) {
68137
68901
  const sqlPositionals = envFromFlag ? positionals : positionals.slice(1);
68138
68902
  const sqlInput = getStringFlag(flags, ["sql"]) ?? sqlPositionals.join(" ");
68139
68903
  const fileInput = getStringFlag(flags, ["file"]);
68140
- const sqlText = fileInput ? (0, import_node_fs8.readFileSync)((0, import_node_path8.resolve)(fileInput), "utf-8") : sqlInput;
68904
+ const sqlText = fileInput ? (0, import_node_fs9.readFileSync)((0, import_node_path9.resolve)(fileInput), "utf-8") : sqlInput;
68141
68905
  if (!sqlText) {
68142
68906
  throw new Error("Usage: eve db sql --env <name> --sql <statement> [--params <json>] [--write]");
68143
68907
  }
@@ -68176,11 +68940,11 @@ function parseJson(value, label) {
68176
68940
  async function handleMigrate(positionals, flags, context2, jsonOutput) {
68177
68941
  const { projectId, envName } = resolveProjectEnv(positionals, flags, context2);
68178
68942
  const migrationsPath = getStringFlag(flags, ["path"]) ?? "db/migrations";
68179
- const fullPath = (0, import_node_path8.resolve)(migrationsPath);
68180
- if (!(0, import_node_fs8.existsSync)(fullPath)) {
68943
+ const fullPath = (0, import_node_path9.resolve)(migrationsPath);
68944
+ if (!(0, import_node_fs9.existsSync)(fullPath)) {
68181
68945
  throw new Error(`Migrations directory not found: ${fullPath}`);
68182
68946
  }
68183
- const files = (0, import_node_fs8.readdirSync)(fullPath).filter((f) => f.endsWith(".sql")).sort();
68947
+ const files = (0, import_node_fs9.readdirSync)(fullPath).filter((f) => f.endsWith(".sql")).sort();
68184
68948
  if (files.length === 0) {
68185
68949
  console.log("No migration files found");
68186
68950
  return;
@@ -68196,7 +68960,7 @@ Example: 20260128100000_create_users.sql`
68196
68960
  }
68197
68961
  const migrations = files.map((file) => ({
68198
68962
  name: file,
68199
- sql: (0, import_node_fs8.readFileSync)((0, import_node_path8.join)(fullPath, file), "utf-8")
68963
+ sql: (0, import_node_fs9.readFileSync)((0, import_node_path9.join)(fullPath, file), "utf-8")
68200
68964
  }));
68201
68965
  console.log(`Found ${migrations.length} migration files`);
68202
68966
  const response = await requestJson(context2, `/projects/${projectId}/envs/${envName}/db/migrate`, {
@@ -68258,12 +69022,12 @@ function handleNew(positionals, flags) {
68258
69022
  ].join("");
68259
69023
  const filename = `${timestamp}_${normalizedDescription}.sql`;
68260
69024
  const migrationsPath = getStringFlag(flags, ["path"]) ?? "db/migrations";
68261
- const fullPath = (0, import_node_path8.resolve)(migrationsPath);
68262
- if (!(0, import_node_fs8.existsSync)(fullPath)) {
68263
- (0, import_node_fs8.mkdirSync)(fullPath, { recursive: true });
69025
+ const fullPath = (0, import_node_path9.resolve)(migrationsPath);
69026
+ if (!(0, import_node_fs9.existsSync)(fullPath)) {
69027
+ (0, import_node_fs9.mkdirSync)(fullPath, { recursive: true });
68264
69028
  }
68265
- const filePath = (0, import_node_path8.join)(fullPath, filename);
68266
- if ((0, import_node_fs8.existsSync)(filePath)) {
69029
+ const filePath = (0, import_node_path9.join)(fullPath, filename);
69030
+ if ((0, import_node_fs9.existsSync)(filePath)) {
68267
69031
  throw new Error(`Migration file already exists: ${filePath}`);
68268
69032
  }
68269
69033
  const template = `-- Migration: ${normalizedDescription}
@@ -68272,10 +69036,10 @@ function handleNew(positionals, flags) {
68272
69036
  -- Write your SQL migration here
68273
69037
 
68274
69038
  `;
68275
- (0, import_node_fs8.writeFileSync)(filePath, template);
69039
+ (0, import_node_fs9.writeFileSync)(filePath, template);
68276
69040
  console.log(`Created: ${filePath}`);
68277
69041
  }
68278
- async function handleStatus2(positionals, flags, context2, jsonOutput) {
69042
+ async function handleStatus3(positionals, flags, context2, jsonOutput) {
68279
69043
  const { projectId, envName } = resolveProjectEnv(positionals, flags, context2);
68280
69044
  const response = await requestJson(context2, `/projects/${projectId}/envs/${envName}/db/managed`);
68281
69045
  if (jsonOutput) {
@@ -68509,24 +69273,24 @@ function formatEventsTable(events) {
68509
69273
  const sourceWidth = Math.max(6, ...events.map((e) => e.source.length));
68510
69274
  const statusWidth = Math.max(6, ...events.map((e) => e.status.length));
68511
69275
  const header = [
68512
- padRight5("ID", idWidth),
68513
- padRight5("Type", typeWidth),
68514
- padRight5("Source", sourceWidth),
68515
- padRight5("Status", statusWidth),
68516
- padRight5("Env", 12),
68517
- padRight5("Branch", 20),
69276
+ padRight6("ID", idWidth),
69277
+ padRight6("Type", typeWidth),
69278
+ padRight6("Source", sourceWidth),
69279
+ padRight6("Status", statusWidth),
69280
+ padRight6("Env", 12),
69281
+ padRight6("Branch", 20),
68518
69282
  "Created"
68519
69283
  ].join(" ");
68520
69284
  console.log(header);
68521
69285
  console.log("-".repeat(header.length));
68522
69286
  for (const event of events) {
68523
69287
  const row = [
68524
- padRight5(event.id, idWidth),
68525
- padRight5(event.type, typeWidth),
68526
- padRight5(event.source, sourceWidth),
68527
- padRight5(event.status, statusWidth),
68528
- padRight5(event.env_name || "-", 12),
68529
- padRight5(event.ref_branch || "-", 20),
69288
+ padRight6(event.id, idWidth),
69289
+ padRight6(event.type, typeWidth),
69290
+ padRight6(event.source, sourceWidth),
69291
+ padRight6(event.status, statusWidth),
69292
+ padRight6(event.env_name || "-", 12),
69293
+ padRight6(event.ref_branch || "-", 20),
68530
69294
  formatDate3(event.created_at)
68531
69295
  ].join(" ");
68532
69296
  console.log(row);
@@ -68579,7 +69343,7 @@ function formatDate3(dateStr) {
68579
69343
  return dateStr;
68580
69344
  }
68581
69345
  }
68582
- function padRight5(str, width) {
69346
+ function padRight6(str, width) {
68583
69347
  if (str.length >= width) return str;
68584
69348
  return str + " ".repeat(width - str.length);
68585
69349
  }
@@ -69391,11 +70155,11 @@ async function fetchGitHubKeys2(username) {
69391
70155
 
69392
70156
  // src/commands/agents.ts
69393
70157
  var import_node_child_process8 = require("node:child_process");
69394
- var import_node_fs9 = require("node:fs");
69395
- var import_node_path9 = require("node:path");
70158
+ var import_node_fs10 = require("node:fs");
70159
+ var import_node_path10 = require("node:path");
69396
70160
  var import_yaml3 = require("yaml");
69397
70161
  function readYamlFile(filePath) {
69398
- const raw = (0, import_node_fs9.readFileSync)(filePath, "utf-8");
70162
+ const raw = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
69399
70163
  const parsed = (0, import_yaml3.parse)(raw);
69400
70164
  if (!parsed || typeof parsed !== "object") {
69401
70165
  throw new Error(`Invalid YAML in ${filePath}`);
@@ -69407,9 +70171,9 @@ function resolveAgentsConfigPaths(repoRoot, manifest) {
69407
70171
  const agentsBlock = xEve["agents"] || {};
69408
70172
  const chatBlock = xEve["chat"] || {};
69409
70173
  const manifestChat = manifest["chat"] || {};
69410
- const agentsPath = (0, import_node_path9.resolve)(repoRoot, pickString(agentsBlock.config_path) ?? "agents/agents.yaml");
69411
- const teamsPath = (0, import_node_path9.resolve)(repoRoot, pickString(agentsBlock.teams_path) ?? "agents/teams.yaml");
69412
- const chatPath = (0, import_node_path9.resolve)(
70174
+ const agentsPath = (0, import_node_path10.resolve)(repoRoot, pickString(agentsBlock.config_path) ?? "agents/agents.yaml");
70175
+ const teamsPath = (0, import_node_path10.resolve)(repoRoot, pickString(agentsBlock.teams_path) ?? "agents/teams.yaml");
70176
+ const chatPath = (0, import_node_path10.resolve)(
69413
70177
  repoRoot,
69414
70178
  pickString(chatBlock.config_path) ?? pickString(manifestChat.config_path) ?? "agents/chat.yaml"
69415
70179
  );
@@ -69419,7 +70183,7 @@ function pickString(value) {
69419
70183
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
69420
70184
  }
69421
70185
  function ensureFileExists(path6, label) {
69422
- if (!(0, import_node_fs9.existsSync)(path6)) {
70186
+ if (!(0, import_node_fs10.existsSync)(path6)) {
69423
70187
  throw new Error(`Missing ${label} at ${path6}. Update manifest config_path or add the file.`);
69424
70188
  }
69425
70189
  return path6;
@@ -69434,9 +70198,9 @@ function isLocalApiUrl(apiUrl) {
69434
70198
  }
69435
70199
  }
69436
70200
  function loadAgentsConfig(repoRoot) {
69437
- const eveDir = (0, import_node_path9.join)(repoRoot, ".eve");
69438
- const manifestPath = (0, import_node_path9.join)(eveDir, "manifest.yaml");
69439
- if ((0, import_node_fs9.existsSync)(manifestPath)) {
70201
+ const eveDir = (0, import_node_path10.join)(repoRoot, ".eve");
70202
+ const manifestPath = (0, import_node_path10.join)(eveDir, "manifest.yaml");
70203
+ if ((0, import_node_fs10.existsSync)(manifestPath)) {
69440
70204
  const manifest = readYamlFile(manifestPath);
69441
70205
  const xEve = manifest["x-eve"] || manifest["x_eve"] || {};
69442
70206
  const policy = xEve["agents"] || null;
@@ -69486,16 +70250,16 @@ async function resolvePacksAndMerge(repoRoot, manifest, projectSlug) {
69486
70250
  }
69487
70251
  }
69488
70252
  const configPaths = resolveAgentsConfigPaths(repoRoot, manifest);
69489
- if ((0, import_node_fs9.existsSync)(configPaths.agentsPath)) {
69490
- const projectAgents = (0, import_yaml3.parse)((0, import_node_fs9.readFileSync)(configPaths.agentsPath, "utf-8")) ?? {};
70253
+ if ((0, import_node_fs10.existsSync)(configPaths.agentsPath)) {
70254
+ const projectAgents = (0, import_yaml3.parse)((0, import_node_fs10.readFileSync)(configPaths.agentsPath, "utf-8")) ?? {};
69491
70255
  mergedAgents = mergeMapConfig(mergedAgents, projectAgents);
69492
70256
  }
69493
- if ((0, import_node_fs9.existsSync)(configPaths.teamsPath)) {
69494
- const projectTeams = (0, import_yaml3.parse)((0, import_node_fs9.readFileSync)(configPaths.teamsPath, "utf-8")) ?? {};
70257
+ if ((0, import_node_fs10.existsSync)(configPaths.teamsPath)) {
70258
+ const projectTeams = (0, import_yaml3.parse)((0, import_node_fs10.readFileSync)(configPaths.teamsPath, "utf-8")) ?? {};
69495
70259
  mergedTeams = mergeMapConfig(mergedTeams, projectTeams);
69496
70260
  }
69497
- if ((0, import_node_fs9.existsSync)(configPaths.chatPath)) {
69498
- const projectChat = (0, import_yaml3.parse)((0, import_node_fs9.readFileSync)(configPaths.chatPath, "utf-8")) ?? {};
70261
+ if ((0, import_node_fs10.existsSync)(configPaths.chatPath)) {
70262
+ const projectChat = (0, import_yaml3.parse)((0, import_node_fs10.readFileSync)(configPaths.chatPath, "utf-8")) ?? {};
69499
70263
  mergedChat = mergeChatConfig(
69500
70264
  mergedChat,
69501
70265
  projectChat
@@ -69524,10 +70288,10 @@ async function resolvePacksAndMerge(repoRoot, manifest, projectSlug) {
69524
70288
  chat_hash: simpleHash(JSON.stringify(mergedChat))
69525
70289
  }
69526
70290
  };
69527
- const eveDir = (0, import_node_path9.join)(repoRoot, ".eve");
69528
- (0, import_node_fs9.mkdirSync)(eveDir, { recursive: true });
69529
- const lockfilePath = (0, import_node_path9.join)(eveDir, "packs.lock.yaml");
69530
- (0, import_node_fs9.writeFileSync)(lockfilePath, (0, import_yaml3.stringify)(lockfile), "utf-8");
70291
+ const eveDir = (0, import_node_path10.join)(repoRoot, ".eve");
70292
+ (0, import_node_fs10.mkdirSync)(eveDir, { recursive: true });
70293
+ const lockfilePath = (0, import_node_path10.join)(eveDir, "packs.lock.yaml");
70294
+ (0, import_node_fs10.writeFileSync)(lockfilePath, (0, import_yaml3.stringify)(lockfile), "utf-8");
69531
70295
  console.log(` \u2713 Lockfile written: .eve/packs.lock.yaml`);
69532
70296
  const packRefs = resolvedPacks.map((p) => ({ id: p.id, source: p.source, ref: p.ref }));
69533
70297
  return {
@@ -69600,7 +70364,7 @@ async function handleAgents(subcommand, positionals, flags, context2) {
69600
70364
  const command = subcommand ?? "config";
69601
70365
  const json = Boolean(flags.json);
69602
70366
  const includeHarnesses = !(getBooleanFlag(flags, ["no-harnesses"]) ?? false);
69603
- const repoRoot = (0, import_node_path9.resolve)(getStringFlag(flags, ["repo-dir", "repo_dir", "dir", "path"]) ?? process.cwd());
70367
+ const repoRoot = (0, import_node_path10.resolve)(getStringFlag(flags, ["repo-dir", "repo_dir", "dir", "path"]) ?? process.cwd());
69604
70368
  switch (command) {
69605
70369
  case "config": {
69606
70370
  const result = loadAgentsConfig(repoRoot);
@@ -69669,8 +70433,8 @@ async function handleAgents(subcommand, positionals, flags, context2) {
69669
70433
  if (dirty && !allowDirty) {
69670
70434
  throw new Error("Working tree is dirty. Commit changes or pass --allow-dirty to sync anyway.");
69671
70435
  }
69672
- const manifestPath = (0, import_node_path9.join)(repoRoot, ".eve", "manifest.yaml");
69673
- if (!(0, import_node_fs9.existsSync)(manifestPath)) {
70436
+ const manifestPath = (0, import_node_path10.join)(repoRoot, ".eve", "manifest.yaml");
70437
+ if (!(0, import_node_fs10.existsSync)(manifestPath)) {
69674
70438
  throw new Error(`Missing manifest at ${manifestPath}. Expected .eve/manifest.yaml.`);
69675
70439
  }
69676
70440
  const manifest = readYamlFile(manifestPath);
@@ -69699,9 +70463,9 @@ async function handleAgents(subcommand, positionals, flags, context2) {
69699
70463
  packRefs = packResult.packRefs;
69700
70464
  } else {
69701
70465
  const configPaths = resolveAgentsConfigPaths(repoRoot, manifest);
69702
- agentsYaml = (0, import_node_fs9.readFileSync)(ensureFileExists(configPaths.agentsPath, "agents config"), "utf-8");
69703
- teamsYaml = (0, import_node_fs9.readFileSync)(ensureFileExists(configPaths.teamsPath, "teams config"), "utf-8");
69704
- chatYaml = (0, import_node_fs9.readFileSync)(ensureFileExists(configPaths.chatPath, "chat config"), "utf-8");
70466
+ agentsYaml = (0, import_node_fs10.readFileSync)(ensureFileExists(configPaths.agentsPath, "agents config"), "utf-8");
70467
+ teamsYaml = (0, import_node_fs10.readFileSync)(ensureFileExists(configPaths.teamsPath, "teams config"), "utf-8");
70468
+ chatYaml = (0, import_node_fs10.readFileSync)(ensureFileExists(configPaths.chatPath, "chat config"), "utf-8");
69705
70469
  }
69706
70470
  let gitSha;
69707
70471
  let branch;
@@ -69784,10 +70548,10 @@ function formatAgentRuntimeStatus(response, orgId) {
69784
70548
  const ageWidth = Math.max(3, ...response.pods.map((pod) => formatAgeSeconds(pod.last_heartbeat_at).length));
69785
70549
  console.log("");
69786
70550
  const header = [
69787
- padRight6("Pod", nameWidth),
69788
- padRight6("Status", statusWidth),
69789
- padRight6("Capacity", capacityWidth),
69790
- padRight6("Age", ageWidth),
70551
+ padRight7("Pod", nameWidth),
70552
+ padRight7("Status", statusWidth),
70553
+ padRight7("Capacity", capacityWidth),
70554
+ padRight7("Age", ageWidth),
69791
70555
  "Last Heartbeat"
69792
70556
  ].join(" ");
69793
70557
  console.log(header);
@@ -69795,15 +70559,15 @@ function formatAgentRuntimeStatus(response, orgId) {
69795
70559
  for (const pod of response.pods) {
69796
70560
  const age = formatAgeSeconds(pod.last_heartbeat_at);
69797
70561
  console.log([
69798
- padRight6(pod.pod_name, nameWidth),
69799
- padRight6(pod.status, statusWidth),
69800
- padRight6(String(pod.capacity), capacityWidth),
69801
- padRight6(age, ageWidth),
70562
+ padRight7(pod.pod_name, nameWidth),
70563
+ padRight7(pod.status, statusWidth),
70564
+ padRight7(String(pod.capacity), capacityWidth),
70565
+ padRight7(age, ageWidth),
69802
70566
  pod.last_heartbeat_at
69803
70567
  ].join(" "));
69804
70568
  }
69805
70569
  }
69806
- function padRight6(value, width) {
70570
+ function padRight7(value, width) {
69807
70571
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
69808
70572
  }
69809
70573
  function formatAgeSeconds(isoDate) {
@@ -70044,8 +70808,8 @@ function ensureSkillsSymlink2(projectRoot) {
70044
70808
  }
70045
70809
 
70046
70810
  // src/commands/release.ts
70047
- var import_node_fs10 = require("node:fs");
70048
- var import_node_path10 = require("node:path");
70811
+ var import_node_fs11 = require("node:fs");
70812
+ var import_node_path11 = require("node:path");
70049
70813
  async function handleRelease2(subcommand, positionals, flags, context2) {
70050
70814
  const json = Boolean(flags.json);
70051
70815
  switch (subcommand) {
@@ -70057,9 +70821,9 @@ async function handleRelease2(subcommand, positionals, flags, context2) {
70057
70821
  let projectId = typeof flags.project === "string" ? flags.project : context2.projectId;
70058
70822
  if (!projectId) {
70059
70823
  const dir = typeof flags.dir === "string" ? flags.dir : process.cwd();
70060
- const manifestPath = (0, import_node_path10.join)(dir, ".eve", "manifest.yaml");
70824
+ const manifestPath = (0, import_node_path11.join)(dir, ".eve", "manifest.yaml");
70061
70825
  try {
70062
- const yaml = (0, import_node_fs10.readFileSync)(manifestPath, "utf-8");
70826
+ const yaml = (0, import_node_fs11.readFileSync)(manifestPath, "utf-8");
70063
70827
  const projectMatch = yaml.match(/^project:\s*(\S+)/m);
70064
70828
  if (projectMatch) {
70065
70829
  projectId = projectMatch[1];
@@ -70108,8 +70872,8 @@ Make sure the release exists and the tag is correct.`
70108
70872
  }
70109
70873
 
70110
70874
  // src/commands/manifest.ts
70111
- var import_node_fs11 = require("node:fs");
70112
- var import_node_path11 = require("node:path");
70875
+ var import_node_fs12 = require("node:fs");
70876
+ var import_node_path12 = require("node:path");
70113
70877
  async function handleManifest(subcommand, positionals, flags, context2) {
70114
70878
  const json = Boolean(flags.json);
70115
70879
  switch (subcommand) {
@@ -70119,11 +70883,11 @@ async function handleManifest(subcommand, positionals, flags, context2) {
70119
70883
  const validateSecretsFlag = flags["validate-secrets"] ?? flags.validate_secrets;
70120
70884
  const validateSecrets = toBoolean(validateSecretsFlag) ?? false;
70121
70885
  const dir = typeof flags.dir === "string" ? flags.dir : process.cwd();
70122
- const manifestPath = getStringFlag(flags, ["path"]) ?? (0, import_node_path11.join)(dir, ".eve", "manifest.yaml");
70886
+ const manifestPath = getStringFlag(flags, ["path"]) ?? (0, import_node_path12.join)(dir, ".eve", "manifest.yaml");
70123
70887
  let manifestYaml;
70124
70888
  if (!useLatest) {
70125
70889
  try {
70126
- manifestYaml = (0, import_node_fs11.readFileSync)(manifestPath, "utf-8");
70890
+ manifestYaml = (0, import_node_fs12.readFileSync)(manifestPath, "utf-8");
70127
70891
  } catch (error) {
70128
70892
  throw new Error(`Failed to read manifest at ${manifestPath}: ${error.message}`);
70129
70893
  }
@@ -70191,11 +70955,11 @@ async function handleManifest(subcommand, positionals, flags, context2) {
70191
70955
  }
70192
70956
 
70193
70957
  // src/commands/packs.ts
70194
- var import_node_fs12 = require("node:fs");
70195
- var import_node_path12 = require("node:path");
70958
+ var import_node_fs13 = require("node:fs");
70959
+ var import_node_path13 = require("node:path");
70196
70960
  var import_yaml4 = require("yaml");
70197
70961
  async function handlePacks(subcommand, _rest, flags, _context) {
70198
- const repoRoot = (0, import_node_path12.resolve)(getStringFlag(flags, ["repo-dir", "repo_dir", "dir", "path"]) ?? process.cwd());
70962
+ const repoRoot = (0, import_node_path13.resolve)(getStringFlag(flags, ["repo-dir", "repo_dir", "dir", "path"]) ?? process.cwd());
70199
70963
  switch (subcommand) {
70200
70964
  case "status": {
70201
70965
  printPacksStatus(repoRoot);
@@ -70211,14 +70975,14 @@ async function handlePacks(subcommand, _rest, flags, _context) {
70211
70975
  }
70212
70976
  }
70213
70977
  function printPacksStatus(repoRoot) {
70214
- const lockfilePath = (0, import_node_path12.join)(repoRoot, ".eve", "packs.lock.yaml");
70215
- const manifestPath = (0, import_node_path12.join)(repoRoot, ".eve", "manifest.yaml");
70216
- if (!(0, import_node_fs12.existsSync)(lockfilePath)) {
70978
+ const lockfilePath = (0, import_node_path13.join)(repoRoot, ".eve", "packs.lock.yaml");
70979
+ const manifestPath = (0, import_node_path13.join)(repoRoot, ".eve", "manifest.yaml");
70980
+ if (!(0, import_node_fs13.existsSync)(lockfilePath)) {
70217
70981
  console.log("No lockfile found at .eve/packs.lock.yaml");
70218
70982
  console.log('Run "eve agents sync" to resolve packs and generate the lockfile.');
70219
70983
  return;
70220
70984
  }
70221
- const lockRaw = (0, import_node_fs12.readFileSync)(lockfilePath, "utf-8");
70985
+ const lockRaw = (0, import_node_fs13.readFileSync)(lockfilePath, "utf-8");
70222
70986
  const lock = (0, import_yaml4.parse)(lockRaw);
70223
70987
  if (!lock || !lock.packs) {
70224
70988
  console.log("Lockfile is empty or malformed.");
@@ -70234,16 +70998,16 @@ function printPacksStatus(repoRoot) {
70234
70998
  const idWidth = Math.max(4, ...lock.packs.map((p) => p.id.length));
70235
70999
  const sourceWidth = Math.max(6, ...lock.packs.map((p) => p.source.length));
70236
71000
  const header = [
70237
- padRight7("Pack", idWidth),
70238
- padRight7("Source", sourceWidth),
71001
+ padRight8("Pack", idWidth),
71002
+ padRight8("Source", sourceWidth),
70239
71003
  "Ref"
70240
71004
  ].join(" ");
70241
71005
  console.log(header);
70242
71006
  console.log("-".repeat(header.length + 12));
70243
71007
  for (const pack of lock.packs) {
70244
71008
  console.log([
70245
- padRight7(pack.id, idWidth),
70246
- padRight7(pack.source, sourceWidth),
71009
+ padRight8(pack.id, idWidth),
71010
+ padRight8(pack.source, sourceWidth),
70247
71011
  pack.ref.substring(0, 12)
70248
71012
  ].join(" "));
70249
71013
  }
@@ -70253,8 +71017,8 @@ function printPacksStatus(repoRoot) {
70253
71017
  console.log(` Agents: ${lock.effective.agents_count}`);
70254
71018
  console.log(` Teams: ${lock.effective.teams_count}`);
70255
71019
  console.log(` Routes: ${lock.effective.routes_count}`);
70256
- if ((0, import_node_fs12.existsSync)(manifestPath)) {
70257
- const manifestRaw = (0, import_node_fs12.readFileSync)(manifestPath, "utf-8");
71020
+ if ((0, import_node_fs13.existsSync)(manifestPath)) {
71021
+ const manifestRaw = (0, import_node_fs13.readFileSync)(manifestPath, "utf-8");
70258
71022
  const manifest = (0, import_yaml4.parse)(manifestRaw);
70259
71023
  if (manifest) {
70260
71024
  const xEve = manifest["x-eve"] ?? manifest["x_eve"] ?? {};
@@ -70299,12 +71063,12 @@ function detectDrift(lock, manifestPacks) {
70299
71063
  }
70300
71064
  function printPacksResolve(repoRoot, dryRun) {
70301
71065
  if (dryRun) {
70302
- const lockfilePath = (0, import_node_path12.join)(repoRoot, ".eve", "packs.lock.yaml");
70303
- const manifestPath = (0, import_node_path12.join)(repoRoot, ".eve", "manifest.yaml");
70304
- if (!(0, import_node_fs12.existsSync)(manifestPath)) {
71066
+ const lockfilePath = (0, import_node_path13.join)(repoRoot, ".eve", "packs.lock.yaml");
71067
+ const manifestPath = (0, import_node_path13.join)(repoRoot, ".eve", "manifest.yaml");
71068
+ if (!(0, import_node_fs13.existsSync)(manifestPath)) {
70305
71069
  throw new Error("No manifest found at .eve/manifest.yaml");
70306
71070
  }
70307
- const manifestRaw = (0, import_node_fs12.readFileSync)(manifestPath, "utf-8");
71071
+ const manifestRaw = (0, import_node_fs13.readFileSync)(manifestPath, "utf-8");
70308
71072
  const manifest = (0, import_yaml4.parse)(manifestRaw);
70309
71073
  if (!manifest) {
70310
71074
  throw new Error("Manifest is empty or malformed.");
@@ -70322,8 +71086,8 @@ function printPacksResolve(repoRoot, dryRun) {
70322
71086
  const refShort = mp.ref ? mp.ref.substring(0, 12) : "(local)";
70323
71087
  console.log(` - ${mp.source} @ ${refShort}`);
70324
71088
  }
70325
- if ((0, import_node_fs12.existsSync)(lockfilePath)) {
70326
- const lockRaw = (0, import_node_fs12.readFileSync)(lockfilePath, "utf-8");
71089
+ if ((0, import_node_fs13.existsSync)(lockfilePath)) {
71090
+ const lockRaw = (0, import_node_fs13.readFileSync)(lockfilePath, "utf-8");
70327
71091
  const lock = (0, import_yaml4.parse)(lockRaw);
70328
71092
  if (lock?.packs) {
70329
71093
  const drift = detectDrift(lock, manifestPacks);
@@ -70352,7 +71116,7 @@ function printPacksResolve(repoRoot, dryRun) {
70352
71116
  console.log("");
70353
71117
  console.log("This will resolve packs, merge configs, write the lockfile, and sync to the API.");
70354
71118
  }
70355
- function padRight7(value, width) {
71119
+ function padRight8(value, width) {
70356
71120
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
70357
71121
  }
70358
71122
 
@@ -71178,8 +71942,8 @@ Cursor: ${result.cursor}`);
71178
71942
  }
71179
71943
 
71180
71944
  // src/commands/migrate.ts
71181
- var import_node_fs13 = require("node:fs");
71182
- var import_node_path13 = require("node:path");
71945
+ var import_node_fs14 = require("node:fs");
71946
+ var import_node_path14 = require("node:path");
71183
71947
  var import_yaml5 = require("yaml");
71184
71948
  async function handleMigrate2(subcommand, _rest, _flags) {
71185
71949
  switch (subcommand) {
@@ -71199,12 +71963,12 @@ async function migrateSkillsToPacks() {
71199
71963
  if (!repoRoot) {
71200
71964
  throw new Error("Not in a git repository. Run this from your project root.");
71201
71965
  }
71202
- const skillsTxtPath = (0, import_node_path13.join)(repoRoot, "skills.txt");
71203
- if (!(0, import_node_fs13.existsSync)(skillsTxtPath)) {
71966
+ const skillsTxtPath = (0, import_node_path14.join)(repoRoot, "skills.txt");
71967
+ if (!(0, import_node_fs14.existsSync)(skillsTxtPath)) {
71204
71968
  console.log("No skills.txt found at repository root. Nothing to migrate.");
71205
71969
  return;
71206
71970
  }
71207
- const content = (0, import_node_fs13.readFileSync)(skillsTxtPath, "utf-8");
71971
+ const content = (0, import_node_fs14.readFileSync)(skillsTxtPath, "utf-8");
71208
71972
  const lines = content.split("\n");
71209
71973
  const localSources = [];
71210
71974
  const remoteSources = [];
@@ -71313,9 +72077,9 @@ async function handleList8(flags, context2, json) {
71313
72077
  const nameWidth = Math.max(12, ...response.managed.map((m) => m.display_name.length));
71314
72078
  const providerWidth = Math.max(8, ...response.managed.map((m) => m.inference_provider.length));
71315
72079
  const header = [
71316
- padRight8("Model Spec", specWidth),
71317
- padRight8("Display Name", nameWidth),
71318
- padRight8("Provider", providerWidth),
72080
+ padRight9("Model Spec", specWidth),
72081
+ padRight9("Display Name", nameWidth),
72082
+ padRight9("Provider", providerWidth),
71319
72083
  "Capabilities"
71320
72084
  ].join(" ");
71321
72085
  console.log(header);
@@ -71326,9 +72090,9 @@ async function handleList8(flags, context2, json) {
71326
72090
  if (model.capabilities.tool_calling) caps.push("tools");
71327
72091
  if (model.capabilities.reasoning) caps.push("reason");
71328
72092
  const row = [
71329
- padRight8(model.model_spec, specWidth),
71330
- padRight8(model.display_name, nameWidth),
71331
- padRight8(model.inference_provider, providerWidth),
72093
+ padRight9(model.model_spec, specWidth),
72094
+ padRight9(model.display_name, nameWidth),
72095
+ padRight9(model.inference_provider, providerWidth),
71332
72096
  caps.join(", ")
71333
72097
  ].join(" ");
71334
72098
  console.log(row);
@@ -71338,16 +72102,95 @@ async function handleList8(flags, context2, json) {
71338
72102
  console.log("");
71339
72103
  console.log('Usage: Set harness_options.model to the model spec (e.g. "managed/deepseek-r1")');
71340
72104
  }
71341
- function padRight8(str, width) {
72105
+ function padRight9(str, width) {
71342
72106
  if (str.length >= width) return str;
71343
72107
  return str + " ".repeat(width - str.length);
71344
72108
  }
71345
72109
 
71346
72110
  // src/commands/access.ts
71347
- var import_node_fs14 = require("node:fs");
71348
- var import_node_path14 = require("node:path");
72111
+ var import_node_fs15 = require("node:fs");
72112
+ var import_node_path15 = require("node:path");
71349
72113
  var readline3 = __toESM(require("node:readline/promises"));
71350
72114
  var import_yaml6 = require("yaml");
72115
+ function resolvePrincipalSelection(flags, options) {
72116
+ const userId = getStringFlag(flags, ["user"]);
72117
+ const spId = getStringFlag(flags, ["service-principal", "sp"]);
72118
+ const groupId = options?.allowGroup ? getStringFlag(flags, ["group"]) : void 0;
72119
+ const selected = [
72120
+ userId ? "user" : null,
72121
+ spId ? "service_principal" : null,
72122
+ groupId ? "group" : null
72123
+ ].filter(Boolean);
72124
+ if (selected.length === 0) {
72125
+ const groupHint = options?.allowGroup ? " or --group <group_id>" : "";
72126
+ throw new Error(`--user <user_id> or --service-principal <sp_id>${groupHint} is required`);
72127
+ }
72128
+ if (selected.length > 1) {
72129
+ throw new Error("Specify exactly one principal selector: --user, --service-principal, or --group");
72130
+ }
72131
+ if (userId) {
72132
+ return { principalType: "user", principalId: userId };
72133
+ }
72134
+ if (spId) {
72135
+ return { principalType: "service_principal", principalId: spId };
72136
+ }
72137
+ return { principalType: "group", principalId: groupId };
72138
+ }
72139
+ function parseScopeJsonFlag(flags) {
72140
+ const raw = getStringFlag(flags, ["scope-json"]);
72141
+ if (!raw) {
72142
+ return void 0;
72143
+ }
72144
+ try {
72145
+ const parsed = JSON.parse(raw);
72146
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
72147
+ throw new Error("must be a JSON object");
72148
+ }
72149
+ return parsed;
72150
+ } catch (error) {
72151
+ const message = error instanceof Error ? error.message : String(error);
72152
+ throw new Error(`Invalid --scope-json: ${message}`);
72153
+ }
72154
+ }
72155
+ function normalizeBindingScope(scope) {
72156
+ if (!scope) {
72157
+ return void 0;
72158
+ }
72159
+ const normalized = {};
72160
+ if (scope.orgfs) {
72161
+ const allow = [...new Set(scope.orgfs.allow_prefixes ?? [])].sort();
72162
+ const readOnly = [...new Set(scope.orgfs.read_only_prefixes ?? [])].sort();
72163
+ if (allow.length > 0 || readOnly.length > 0) {
72164
+ normalized.orgfs = {};
72165
+ if (allow.length > 0) normalized.orgfs.allow_prefixes = allow;
72166
+ if (readOnly.length > 0) normalized.orgfs.read_only_prefixes = readOnly;
72167
+ }
72168
+ }
72169
+ if (scope.orgdocs) {
72170
+ const allow = [...new Set(scope.orgdocs.allow_prefixes ?? [])].sort();
72171
+ const readOnly = [...new Set(scope.orgdocs.read_only_prefixes ?? [])].sort();
72172
+ if (allow.length > 0 || readOnly.length > 0) {
72173
+ normalized.orgdocs = {};
72174
+ if (allow.length > 0) normalized.orgdocs.allow_prefixes = allow;
72175
+ if (readOnly.length > 0) normalized.orgdocs.read_only_prefixes = readOnly;
72176
+ }
72177
+ }
72178
+ if (scope.envdb) {
72179
+ const schemas = [...new Set(scope.envdb.schemas ?? [])].sort();
72180
+ const tables = [...new Set(scope.envdb.tables ?? [])].sort();
72181
+ if (schemas.length > 0 || tables.length > 0) {
72182
+ normalized.envdb = {};
72183
+ if (schemas.length > 0) normalized.envdb.schemas = schemas;
72184
+ if (tables.length > 0) normalized.envdb.tables = tables;
72185
+ }
72186
+ }
72187
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
72188
+ }
72189
+ function scopeEquals(left, right) {
72190
+ const normalizedLeft = normalizeBindingScope(left);
72191
+ const normalizedRight = normalizeBindingScope(right);
72192
+ return JSON.stringify(normalizedLeft ?? null) === JSON.stringify(normalizedRight ?? null);
72193
+ }
71351
72194
  async function handleAccess(subcommand, rest, flags, context2) {
71352
72195
  const json = Boolean(flags.json);
71353
72196
  switch (subcommand) {
@@ -71363,6 +72206,10 @@ async function handleAccess(subcommand, rest, flags, context2) {
71363
72206
  return handleUnbind(flags, context2, json);
71364
72207
  case "bindings":
71365
72208
  return handleBindings(rest[0], flags, context2, json);
72209
+ case "groups":
72210
+ return handleGroups(rest[0], rest.slice(1), flags, context2, json);
72211
+ case "memberships":
72212
+ return handleMemberships(flags, context2, json);
71366
72213
  case "validate":
71367
72214
  return handleValidate(flags, json);
71368
72215
  case "plan":
@@ -71371,38 +72218,44 @@ async function handleAccess(subcommand, rest, flags, context2) {
71371
72218
  return handleSync(flags, context2, json);
71372
72219
  default:
71373
72220
  throw new Error(
71374
- "Usage: eve access <subcommand> [flags]\n\nCommands:\n can Check if a principal can perform an action\n explain Explain permission resolution chain\n roles Manage custom access roles (create|list|show|update|delete)\n bind Bind a custom role to a principal\n unbind Remove a role binding from a principal\n bindings List access bindings\n validate Validate an .eve/access.yaml file\n plan Show changes needed to sync access.yaml to an org\n sync Apply access.yaml to an org (create/update/prune)"
72221
+ "Usage: eve access <subcommand> [flags]\n\nCommands:\n can Check if a principal can perform an action\n explain Explain permission resolution chain\n roles Manage custom access roles (create|list|show|update|delete)\n bind Bind a custom role to a principal\n unbind Remove a role binding from a principal\n bindings List access bindings\n groups Manage access groups and members\n memberships Inspect principal memberships and effective scopes\n validate Validate an .eve/access.yaml file\n plan Show changes needed to sync access.yaml to an org\n sync Apply access.yaml to an org (create/update/prune)"
71375
72222
  );
71376
72223
  }
71377
72224
  }
71378
72225
  async function handleCan(flags, context2, json) {
71379
72226
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
71380
- const userId = getStringFlag(flags, ["user"]);
71381
- const spId = getStringFlag(flags, ["service-principal", "sp"]);
71382
72227
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
71383
72228
  const permission = getStringFlag(flags, ["permission"]);
72229
+ const resourceType = getStringFlag(flags, ["resource-type"]);
72230
+ const resourceId = getStringFlag(flags, ["resource", "resource-id"]);
72231
+ const action = getStringFlag(flags, ["action"]);
71384
72232
  if (!orgId) {
71385
72233
  throw new Error("--org is required (or set a default org in your profile)");
71386
72234
  }
71387
72235
  if (!permission) {
71388
72236
  throw new Error("--permission is required");
71389
72237
  }
71390
- if (!userId && !spId) {
71391
- throw new Error("--user <user_id> or --service-principal <sp_id> is required");
71392
- }
71393
- if (userId && spId) {
71394
- throw new Error("Specify either --user or --service-principal, not both");
72238
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
72239
+ if ((resourceType || action) && !resourceId) {
72240
+ throw new Error("--resource is required when --resource-type or --action is provided");
71395
72241
  }
71396
- const principalType = userId ? "user" : "service_principal";
71397
- const principalId = userId ?? spId;
71398
72242
  const params = new URLSearchParams({
71399
- principal_type: principalType,
71400
- principal_id: principalId,
72243
+ principal_type: principal.principalType,
72244
+ principal_id: principal.principalId,
71401
72245
  permission
71402
72246
  });
71403
72247
  if (projectId) {
71404
72248
  params.set("project_id", projectId);
71405
72249
  }
72250
+ if (resourceType) {
72251
+ params.set("resource_type", resourceType);
72252
+ }
72253
+ if (resourceId) {
72254
+ params.set("resource_id", resourceId);
72255
+ }
72256
+ if (action) {
72257
+ params.set("action", action);
72258
+ }
71406
72259
  const response = await requestJson(
71407
72260
  context2,
71408
72261
  `/orgs/${orgId}/access/can?${params.toString()}`
@@ -71413,35 +72266,47 @@ async function handleCan(flags, context2, json) {
71413
72266
  }
71414
72267
  const label = response.allowed ? "ALLOWED" : "DENIED";
71415
72268
  console.log(`${label} (source: ${response.source})`);
72269
+ if (response.resource) {
72270
+ const resourceStatus = response.resource.scope_matched ? "scope match" : "scope denied";
72271
+ console.log(
72272
+ `Resource: ${response.resource.type}:${response.resource.id} [${response.resource.action}] (${resourceStatus})`
72273
+ );
72274
+ }
71416
72275
  }
71417
72276
  async function handleExplain(flags, context2, json) {
71418
72277
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
71419
- const userId = getStringFlag(flags, ["user"]);
71420
- const spId = getStringFlag(flags, ["service-principal", "sp"]);
71421
72278
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
71422
72279
  const permission = getStringFlag(flags, ["permission"]);
72280
+ const resourceType = getStringFlag(flags, ["resource-type"]);
72281
+ const resourceId = getStringFlag(flags, ["resource", "resource-id"]);
72282
+ const action = getStringFlag(flags, ["action"]);
71423
72283
  if (!orgId) {
71424
72284
  throw new Error("--org is required (or set a default org in your profile)");
71425
72285
  }
71426
72286
  if (!permission) {
71427
72287
  throw new Error("--permission is required");
71428
72288
  }
71429
- if (!userId && !spId) {
71430
- throw new Error("--user <user_id> or --service-principal <sp_id> is required");
71431
- }
71432
- if (userId && spId) {
71433
- throw new Error("Specify either --user or --service-principal, not both");
72289
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
72290
+ if ((resourceType || action) && !resourceId) {
72291
+ throw new Error("--resource is required when --resource-type or --action is provided");
71434
72292
  }
71435
- const principalType = userId ? "user" : "service_principal";
71436
- const principalId = userId ?? spId;
71437
72293
  const params = new URLSearchParams({
71438
- principal_type: principalType,
71439
- principal_id: principalId,
72294
+ principal_type: principal.principalType,
72295
+ principal_id: principal.principalId,
71440
72296
  permission
71441
72297
  });
71442
72298
  if (projectId) {
71443
72299
  params.set("project_id", projectId);
71444
72300
  }
72301
+ if (resourceType) {
72302
+ params.set("resource_type", resourceType);
72303
+ }
72304
+ if (resourceId) {
72305
+ params.set("resource_id", resourceId);
72306
+ }
72307
+ if (action) {
72308
+ params.set("action", action);
72309
+ }
71445
72310
  const response = await requestJson(
71446
72311
  context2,
71447
72312
  `/orgs/${orgId}/access/explain?${params.toString()}`
@@ -71452,13 +72317,20 @@ async function handleExplain(flags, context2, json) {
71452
72317
  }
71453
72318
  console.log(`Permission: ${response.permission}`);
71454
72319
  console.log(`Result: ${response.result}`);
72320
+ if (response.resource) {
72321
+ const resourceStatus = response.resource.scope_matched ? "scope match" : "scope denied";
72322
+ console.log(
72323
+ `Resource: ${response.resource.type}:${response.resource.id} [${response.resource.action}] (${resourceStatus})`
72324
+ );
72325
+ }
71455
72326
  if (response.grants.length > 0) {
71456
72327
  console.log("Grants found:");
71457
72328
  for (const grant of response.grants) {
71458
72329
  const roleLabel = grant.role ? `: ${grant.role}` : "";
71459
72330
  const permCount = grant.permissions.length;
71460
72331
  const status = grant.has_permission ? "has permission" : `missing ${response.permission}`;
71461
- console.log(` - ${grant.source}${roleLabel} -> [${permCount} permissions] (${status})`);
72332
+ const scopeSuffix = grant.scope_match === void 0 ? "" : grant.scope_match ? " [scope:match]" : ` [scope:deny${grant.scope_reason ? `: ${grant.scope_reason}` : ""}]`;
72333
+ console.log(` - ${grant.source}${roleLabel} -> [${permCount} permissions] (${status})${scopeSuffix}`);
71462
72334
  }
71463
72335
  } else {
71464
72336
  console.log("Grants found: none");
@@ -71638,32 +72510,27 @@ async function handleRoles(action, positionals, flags, context2, json) {
71638
72510
  }
71639
72511
  async function handleBind(flags, context2, json) {
71640
72512
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
71641
- const userId = getStringFlag(flags, ["user"]);
71642
- const spId = getStringFlag(flags, ["service-principal", "sp"]);
71643
72513
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
71644
72514
  const roleName = getStringFlag(flags, ["role"]);
72515
+ const scopeJson = parseScopeJsonFlag(flags);
71645
72516
  if (!orgId) {
71646
72517
  throw new Error("--org is required (or set a default org in your profile)");
71647
72518
  }
71648
72519
  if (!roleName) {
71649
72520
  throw new Error("--role <role_name> is required");
71650
72521
  }
71651
- if (!userId && !spId) {
71652
- throw new Error("--user <user_id> or --service-principal <sp_id> is required");
71653
- }
71654
- if (userId && spId) {
71655
- throw new Error("Specify either --user or --service-principal, not both");
71656
- }
71657
- const principalType = userId ? "user" : "service_principal";
71658
- const principalId = userId ?? spId;
72522
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
71659
72523
  const body = {
71660
72524
  role_name: roleName,
71661
- principal_type: principalType,
71662
- principal_id: principalId
72525
+ principal_type: principal.principalType,
72526
+ principal_id: principal.principalId
71663
72527
  };
71664
72528
  if (projectId) {
71665
72529
  body.project_id = projectId;
71666
72530
  }
72531
+ if (scopeJson) {
72532
+ body.scope_json = scopeJson;
72533
+ }
71667
72534
  const response = await requestJson(
71668
72535
  context2,
71669
72536
  `/orgs/${orgId}/access/bindings`,
@@ -71674,12 +72541,10 @@ async function handleBind(flags, context2, json) {
71674
72541
  return;
71675
72542
  }
71676
72543
  const scopeLabel = response.project_id ? `project ${response.project_id}` : `org ${orgId}`;
71677
- console.log(`Bound role '${response.role_name}' to ${principalType} ${principalId} on ${scopeLabel}`);
72544
+ console.log(`Bound role '${response.role_name}' to ${principal.principalType} ${principal.principalId} on ${scopeLabel}`);
71678
72545
  }
71679
72546
  async function handleUnbind(flags, context2, json) {
71680
72547
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
71681
- const userId = getStringFlag(flags, ["user"]);
71682
- const spId = getStringFlag(flags, ["service-principal", "sp"]);
71683
72548
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
71684
72549
  const roleName = getStringFlag(flags, ["role"]);
71685
72550
  if (!orgId) {
@@ -71688,17 +72553,10 @@ async function handleUnbind(flags, context2, json) {
71688
72553
  if (!roleName) {
71689
72554
  throw new Error("--role <role_name> is required");
71690
72555
  }
71691
- if (!userId && !spId) {
71692
- throw new Error("--user <user_id> or --service-principal <sp_id> is required");
71693
- }
71694
- if (userId && spId) {
71695
- throw new Error("Specify either --user or --service-principal, not both");
71696
- }
71697
- const principalType = userId ? "user" : "service_principal";
71698
- const principalId = userId ?? spId;
72556
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
71699
72557
  const params = new URLSearchParams({
71700
- principal_type: principalType,
71701
- principal_id: principalId
72558
+ principal_type: principal.principalType,
72559
+ principal_id: principal.principalId
71702
72560
  });
71703
72561
  if (projectId) {
71704
72562
  params.set("project_id", projectId);
@@ -71710,14 +72568,14 @@ async function handleUnbind(flags, context2, json) {
71710
72568
  const bindings = unwrapListResponse(bindingsResponse);
71711
72569
  const matching = bindings.find((b) => {
71712
72570
  if (b.role_name !== roleName) return false;
71713
- if (b.principal_type !== principalType) return false;
71714
- if (b.principal_id !== principalId) return false;
72571
+ if (b.principal_type !== principal.principalType) return false;
72572
+ if (b.principal_id !== principal.principalId) return false;
71715
72573
  if (projectId) return b.project_id === projectId;
71716
72574
  return b.project_id === null;
71717
72575
  });
71718
72576
  if (!matching) {
71719
72577
  throw new Error(
71720
- `No binding found for role '${roleName}' on ${principalType} ${principalId}` + (projectId ? ` in project ${projectId}` : " at org level")
72578
+ `No binding found for role '${roleName}' on ${principal.principalType} ${principal.principalId}` + (projectId ? ` in project ${projectId}` : " at org level")
71721
72579
  );
71722
72580
  }
71723
72581
  await requestJson(
@@ -71730,7 +72588,7 @@ async function handleUnbind(flags, context2, json) {
71730
72588
  return;
71731
72589
  }
71732
72590
  const scopeLabel = projectId ? `project ${projectId}` : `org ${orgId}`;
71733
- console.log(`Unbound role '${roleName}' from ${principalType} ${principalId} on ${scopeLabel}`);
72591
+ console.log(`Unbound role '${roleName}' from ${principal.principalType} ${principal.principalId} on ${scopeLabel}`);
71734
72592
  }
71735
72593
  async function handleBindings(action, flags, context2, json) {
71736
72594
  if (action && action !== "list") {
@@ -71762,16 +72620,262 @@ async function handleBindings(action, flags, context2, json) {
71762
72620
  console.log(`${b.role_name} -> ${b.principal_type} ${b.principal_id} (${scope})`);
71763
72621
  }
71764
72622
  }
72623
+ async function handleGroups(action, positionals, flags, context2, json) {
72624
+ const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
72625
+ if (!orgId) {
72626
+ throw new Error("--org is required (or set a default org in your profile)");
72627
+ }
72628
+ if (action === "members") {
72629
+ return handleGroupMembers(positionals[0], positionals.slice(1), flags, context2, json);
72630
+ }
72631
+ switch (action) {
72632
+ case "create": {
72633
+ const name = positionals[0] ?? getStringFlag(flags, ["name"]);
72634
+ const slug = getStringFlag(flags, ["slug"]);
72635
+ const description = getStringFlag(flags, ["description"]);
72636
+ if (!name) {
72637
+ throw new Error("Usage: eve access groups create <name> --org <org_id> [--slug <slug>] [--description <text>]");
72638
+ }
72639
+ const response = await requestJson(context2, `/orgs/${orgId}/access/groups`, {
72640
+ method: "POST",
72641
+ body: { name, slug, description }
72642
+ });
72643
+ if (json) {
72644
+ outputJson(response, json);
72645
+ return;
72646
+ }
72647
+ console.log(`Created group '${response.name}' (${response.id})`);
72648
+ console.log(` Slug: ${response.slug}`);
72649
+ if (response.description) {
72650
+ console.log(` Description: ${response.description}`);
72651
+ }
72652
+ return;
72653
+ }
72654
+ case "list": {
72655
+ const listResponse = await requestJson(
72656
+ context2,
72657
+ `/orgs/${orgId}/access/groups`
72658
+ );
72659
+ const groups = unwrapListResponse(listResponse);
72660
+ if (json) {
72661
+ outputJson({ data: groups }, json);
72662
+ return;
72663
+ }
72664
+ if (groups.length === 0) {
72665
+ console.log("No access groups found.");
72666
+ return;
72667
+ }
72668
+ for (const group of groups) {
72669
+ const desc = group.description ? ` - ${group.description}` : "";
72670
+ console.log(`${group.slug} (${group.id})${desc}`);
72671
+ }
72672
+ return;
72673
+ }
72674
+ case "show": {
72675
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72676
+ if (!group) {
72677
+ throw new Error("Usage: eve access groups show <group_id_or_slug> --org <org_id>");
72678
+ }
72679
+ const response = await requestJson(
72680
+ context2,
72681
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}`
72682
+ );
72683
+ if (json) {
72684
+ outputJson(response, json);
72685
+ return;
72686
+ }
72687
+ console.log(`Name: ${response.name}`);
72688
+ console.log(`ID: ${response.id}`);
72689
+ console.log(`Slug: ${response.slug}`);
72690
+ if (response.description) {
72691
+ console.log(`Description: ${response.description}`);
72692
+ }
72693
+ console.log(`Created: ${response.created_at}`);
72694
+ console.log(`Updated: ${response.updated_at}`);
72695
+ return;
72696
+ }
72697
+ case "update": {
72698
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72699
+ if (!group) {
72700
+ throw new Error("Usage: eve access groups update <group_id_or_slug> --org <org_id> [--name <name>] [--slug <slug>] [--description <text>]");
72701
+ }
72702
+ const name = getStringFlag(flags, ["name"]);
72703
+ const slug = getStringFlag(flags, ["slug"]);
72704
+ const description = getStringFlag(flags, ["description"]);
72705
+ if (name === void 0 && slug === void 0 && description === void 0) {
72706
+ throw new Error("Nothing to update. Use --name, --slug, or --description");
72707
+ }
72708
+ const response = await requestJson(
72709
+ context2,
72710
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}`,
72711
+ {
72712
+ method: "PATCH",
72713
+ body: {
72714
+ name,
72715
+ slug,
72716
+ description
72717
+ }
72718
+ }
72719
+ );
72720
+ if (json) {
72721
+ outputJson(response, json);
72722
+ return;
72723
+ }
72724
+ console.log(`Updated group '${response.name}' (${response.id})`);
72725
+ console.log(` Slug: ${response.slug}`);
72726
+ if (response.description) {
72727
+ console.log(` Description: ${response.description}`);
72728
+ }
72729
+ return;
72730
+ }
72731
+ case "delete": {
72732
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72733
+ if (!group) {
72734
+ throw new Error("Usage: eve access groups delete <group_id_or_slug> --org <org_id>");
72735
+ }
72736
+ await requestJson(context2, `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}`, {
72737
+ method: "DELETE"
72738
+ });
72739
+ if (json) {
72740
+ outputJson({ deleted: true, group }, json);
72741
+ return;
72742
+ }
72743
+ console.log(`Deleted group '${group}'`);
72744
+ return;
72745
+ }
72746
+ default:
72747
+ throw new Error(
72748
+ 'Usage: eve access groups <create|list|show|update|delete|members> [args] [flags]\n\nExamples:\n eve access groups create "Product Management" --org org_xxx\n eve access groups list --org org_xxx\n eve access groups members list pm-team --org org_xxx'
72749
+ );
72750
+ }
72751
+ }
72752
+ async function handleGroupMembers(action, positionals, flags, context2, json) {
72753
+ const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
72754
+ if (!orgId) {
72755
+ throw new Error("--org is required (or set a default org in your profile)");
72756
+ }
72757
+ switch (action) {
72758
+ case "add": {
72759
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72760
+ if (!group) {
72761
+ throw new Error("Usage: eve access groups members add <group> --org <org_id> (--user <user_id> | --service-principal <sp_id>)");
72762
+ }
72763
+ const principal = resolvePrincipalSelection(flags);
72764
+ const response = await requestJson(
72765
+ context2,
72766
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}/members`,
72767
+ {
72768
+ method: "POST",
72769
+ body: {
72770
+ principal_type: principal.principalType,
72771
+ principal_id: principal.principalId
72772
+ }
72773
+ }
72774
+ );
72775
+ if (json) {
72776
+ outputJson(response, json);
72777
+ return;
72778
+ }
72779
+ console.log(`Added ${response.principal_type} ${response.principal_id} to group ${group}`);
72780
+ return;
72781
+ }
72782
+ case "list": {
72783
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72784
+ if (!group) {
72785
+ throw new Error("Usage: eve access groups members list <group> --org <org_id>");
72786
+ }
72787
+ const membersResponse = await requestJson(
72788
+ context2,
72789
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}/members`
72790
+ );
72791
+ const members = unwrapListResponse(membersResponse);
72792
+ if (json) {
72793
+ outputJson({ data: members }, json);
72794
+ return;
72795
+ }
72796
+ if (members.length === 0) {
72797
+ console.log("No group members found.");
72798
+ return;
72799
+ }
72800
+ for (const member of members) {
72801
+ console.log(`${member.principal_type} ${member.principal_id}`);
72802
+ }
72803
+ return;
72804
+ }
72805
+ case "remove": {
72806
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72807
+ if (!group) {
72808
+ throw new Error("Usage: eve access groups members remove <group> --org <org_id> (--user <user_id> | --service-principal <sp_id>)");
72809
+ }
72810
+ const principal = resolvePrincipalSelection(flags);
72811
+ await requestJson(
72812
+ context2,
72813
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}/members/${principal.principalType}/${principal.principalId}`,
72814
+ { method: "DELETE" }
72815
+ );
72816
+ if (json) {
72817
+ outputJson({ removed: true, group, principal: principal.principalId }, json);
72818
+ return;
72819
+ }
72820
+ console.log(`Removed ${principal.principalType} ${principal.principalId} from group ${group}`);
72821
+ return;
72822
+ }
72823
+ default:
72824
+ throw new Error(
72825
+ "Usage: eve access groups members <add|list|remove> <group> --org <org_id> [--user <id> | --service-principal <id>]"
72826
+ );
72827
+ }
72828
+ }
72829
+ async function handleMemberships(flags, context2, json) {
72830
+ const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
72831
+ if (!orgId) {
72832
+ throw new Error("--org is required (or set a default org in your profile)");
72833
+ }
72834
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
72835
+ const response = await requestJson(
72836
+ context2,
72837
+ `/orgs/${orgId}/access/principals/${principal.principalType}/${principal.principalId}/memberships`
72838
+ );
72839
+ if (json) {
72840
+ outputJson(response, json);
72841
+ return;
72842
+ }
72843
+ console.log(`Principal: ${response.principal_type} ${response.principal_id}`);
72844
+ if (response.base.org_role) {
72845
+ console.log(`Org role: ${response.base.org_role}`);
72846
+ }
72847
+ if (response.base.project_roles.length > 0) {
72848
+ console.log(`Project roles: ${response.base.project_roles.length}`);
72849
+ }
72850
+ if (response.groups.length > 0) {
72851
+ console.log(`Groups: ${response.groups.map((group) => group.slug).join(", ")}`);
72852
+ } else {
72853
+ console.log("Groups: none");
72854
+ }
72855
+ console.log(`Effective permissions: ${response.effective_permissions.length}`);
72856
+ if (response.effective_scopes.orgfs.allow_prefixes.length > 0) {
72857
+ console.log(`orgfs allow: ${response.effective_scopes.orgfs.allow_prefixes.join(", ")}`);
72858
+ }
72859
+ if (response.effective_scopes.orgdocs.allow_prefixes.length > 0) {
72860
+ console.log(`orgdocs allow: ${response.effective_scopes.orgdocs.allow_prefixes.join(", ")}`);
72861
+ }
72862
+ if (response.effective_scopes.envdb.schemas.length > 0 || response.effective_scopes.envdb.tables.length > 0) {
72863
+ const schemas = response.effective_scopes.envdb.schemas.join(", ") || "-";
72864
+ const tables = response.effective_scopes.envdb.tables.join(", ") || "-";
72865
+ console.log(`envdb schemas: ${schemas}`);
72866
+ console.log(`envdb tables: ${tables}`);
72867
+ }
72868
+ }
71765
72869
  var DEFAULT_ACCESS_YAML = ".eve/access.yaml";
71766
72870
  function resolveFilePath(flags) {
71767
72871
  const filePath = getStringFlag(flags, ["file", "f"]) ?? DEFAULT_ACCESS_YAML;
71768
- return (0, import_node_path14.resolve)(process.cwd(), filePath);
72872
+ return (0, import_node_path15.resolve)(process.cwd(), filePath);
71769
72873
  }
71770
72874
  function loadAccessYaml(filePath) {
71771
- if (!(0, import_node_fs14.existsSync)(filePath)) {
72875
+ if (!(0, import_node_fs15.existsSync)(filePath)) {
71772
72876
  throw new Error(`File not found: ${filePath}`);
71773
72877
  }
71774
- const raw = (0, import_node_fs14.readFileSync)(filePath, "utf-8");
72878
+ const raw = (0, import_node_fs15.readFileSync)(filePath, "utf-8");
71775
72879
  let parsed;
71776
72880
  try {
71777
72881
  parsed = (0, import_yaml6.parse)(raw);
@@ -71789,10 +72893,62 @@ ${issues.join("\n")}`);
71789
72893
  }
71790
72894
  return result.data;
71791
72895
  }
72896
+ function permissionScopeType(permission) {
72897
+ if (permission.startsWith("orgfs:")) return "orgfs";
72898
+ if (permission.startsWith("orgdocs:")) return "orgdocs";
72899
+ if (permission.startsWith("envdb:")) return "envdb";
72900
+ return null;
72901
+ }
72902
+ function hasPrefixScope(scope, resourceType, requireWritable) {
72903
+ const resourceScope = scope?.[resourceType];
72904
+ if (!resourceScope) {
72905
+ return false;
72906
+ }
72907
+ const allowCount = resourceScope.allow_prefixes?.length ?? 0;
72908
+ const readOnlyCount = resourceScope.read_only_prefixes?.length ?? 0;
72909
+ if (requireWritable) {
72910
+ return allowCount > 0;
72911
+ }
72912
+ return allowCount > 0 || readOnlyCount > 0;
72913
+ }
72914
+ function hasEnvDbScope(scope) {
72915
+ const envdb = scope?.envdb;
72916
+ if (!envdb) {
72917
+ return false;
72918
+ }
72919
+ return (envdb.schemas?.length ?? 0) > 0 || (envdb.tables?.length ?? 0) > 0;
72920
+ }
72921
+ function groupDefaultName(slug) {
72922
+ return slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
72923
+ }
72924
+ function declaredMemberKey(member) {
72925
+ return `${member.type}:${member.id}`;
72926
+ }
72927
+ function apiMemberKey(member) {
72928
+ return `${member.principal_type}:${member.principal_id}`;
72929
+ }
72930
+ function bindingIdentityKey(roleName, principalType, principalId, projectId) {
72931
+ const scope = projectId ?? "org";
72932
+ return `${roleName}|${principalType}|${principalId}|${scope}`;
72933
+ }
72934
+ function bindingMatchKey(roleName, principalType, principalId, projectId, scope) {
72935
+ const normalizedScope = normalizeBindingScope(scope);
72936
+ return `${bindingIdentityKey(roleName, principalType, principalId, projectId)}|${JSON.stringify(normalizedScope ?? null)}`;
72937
+ }
72938
+ function formatBindingScope(scope) {
72939
+ const normalized = normalizeBindingScope(scope);
72940
+ if (!normalized) {
72941
+ return "(none)";
72942
+ }
72943
+ return JSON.stringify(normalized);
72944
+ }
71792
72945
  function semanticValidate(yaml) {
71793
72946
  const errors = [];
71794
- const roleNames = new Set(Object.keys(yaml.access.roles ?? {}));
71795
- for (const [name, role] of Object.entries(yaml.access.roles ?? {})) {
72947
+ const roles = yaml.access.roles ?? {};
72948
+ const groups = yaml.access.groups ?? {};
72949
+ const roleNames = new Set(Object.keys(roles));
72950
+ const groupSlugs = new Set(Object.keys(groups));
72951
+ for (const [name, role] of Object.entries(roles)) {
71796
72952
  for (const perm of role.permissions) {
71797
72953
  if (!PERMISSION_SET.has(perm)) {
71798
72954
  errors.push({
@@ -71802,6 +72958,23 @@ function semanticValidate(yaml) {
71802
72958
  }
71803
72959
  }
71804
72960
  }
72961
+ for (const [slug, group] of Object.entries(groups)) {
72962
+ const seenMembers = /* @__PURE__ */ new Set();
72963
+ const members = group.members ?? [];
72964
+ for (let i = 0; i < members.length; i++) {
72965
+ const member = members[i];
72966
+ const key = declaredMemberKey(member);
72967
+ if (seenMembers.has(key)) {
72968
+ errors.push({
72969
+ path: `access.groups.${slug}.members[${i}]`,
72970
+ message: `Duplicate group member '${key}'`
72971
+ });
72972
+ continue;
72973
+ }
72974
+ seenMembers.add(key);
72975
+ }
72976
+ }
72977
+ const seenBindingKeys = /* @__PURE__ */ new Set();
71805
72978
  const bindings = yaml.access.bindings ?? [];
71806
72979
  for (let i = 0; i < bindings.length; i++) {
71807
72980
  const binding = bindings[i];
@@ -71813,18 +72986,73 @@ function semanticValidate(yaml) {
71813
72986
  });
71814
72987
  }
71815
72988
  }
71816
- if (binding.scope === "project" && !binding.project_id) {
72989
+ if (binding.project_id !== void 0 && binding.project_id.trim() === "") {
72990
+ errors.push({
72991
+ path: `access.bindings[${i}].project_id`,
72992
+ message: "project_id cannot be empty when provided"
72993
+ });
72994
+ }
72995
+ if (binding.subject.type === "group" && !groupSlugs.has(binding.subject.id)) {
72996
+ errors.push({
72997
+ path: `access.bindings[${i}].subject.id`,
72998
+ message: `Group '${binding.subject.id}' is not defined in access.groups`
72999
+ });
73000
+ }
73001
+ const normalizedScope = normalizeBindingScope(binding.scope);
73002
+ const requiredScopeTypes = /* @__PURE__ */ new Set();
73003
+ let needsWritableOrgFsScope = false;
73004
+ let needsWritableOrgDocsScope = false;
73005
+ for (const roleName of binding.roles) {
73006
+ const role = roles[roleName];
73007
+ if (!role) continue;
73008
+ for (const permission of role.permissions) {
73009
+ const scopeType = permissionScopeType(permission);
73010
+ if (scopeType) {
73011
+ requiredScopeTypes.add(scopeType);
73012
+ }
73013
+ if (permission === "orgfs:write" || permission === "orgfs:admin") {
73014
+ needsWritableOrgFsScope = true;
73015
+ }
73016
+ if (permission === "orgdocs:write" || permission === "orgdocs:admin") {
73017
+ needsWritableOrgDocsScope = true;
73018
+ }
73019
+ }
73020
+ }
73021
+ if (requiredScopeTypes.has("orgfs") && !hasPrefixScope(normalizedScope, "orgfs", needsWritableOrgFsScope)) {
73022
+ errors.push({
73023
+ path: `access.bindings[${i}].scope`,
73024
+ message: needsWritableOrgFsScope ? "Binding includes orgfs write/admin permissions but scope.orgfs.allow_prefixes is missing" : "Binding includes orgfs permissions but scope.orgfs prefixes are missing"
73025
+ });
73026
+ }
73027
+ if (requiredScopeTypes.has("orgdocs") && !hasPrefixScope(normalizedScope, "orgdocs", needsWritableOrgDocsScope)) {
71817
73028
  errors.push({
71818
- path: `access.bindings[${i}]`,
71819
- message: "Project-scoped binding requires a project_id"
73029
+ path: `access.bindings[${i}].scope`,
73030
+ message: needsWritableOrgDocsScope ? "Binding includes orgdocs write/admin permissions but scope.orgdocs.allow_prefixes is missing" : "Binding includes orgdocs permissions but scope.orgdocs prefixes are missing"
71820
73031
  });
71821
73032
  }
71822
- if (binding.scope === "org" && binding.project_id) {
73033
+ if (requiredScopeTypes.has("envdb") && !hasEnvDbScope(normalizedScope)) {
71823
73034
  errors.push({
71824
- path: `access.bindings[${i}]`,
71825
- message: "Org-scoped binding should not have a project_id"
73035
+ path: `access.bindings[${i}].scope`,
73036
+ message: "Binding includes envdb permissions but scope.envdb.schemas/tables is missing"
71826
73037
  });
71827
73038
  }
73039
+ for (const roleName of binding.roles) {
73040
+ const dedupeKey = bindingMatchKey(
73041
+ roleName,
73042
+ binding.subject.type,
73043
+ binding.subject.id,
73044
+ binding.project_id ?? null,
73045
+ normalizedScope
73046
+ );
73047
+ if (seenBindingKeys.has(dedupeKey)) {
73048
+ errors.push({
73049
+ path: `access.bindings[${i}]`,
73050
+ message: `Duplicate binding tuple for role '${roleName}' and subject '${binding.subject.type}:${binding.subject.id}'`
73051
+ });
73052
+ } else {
73053
+ seenBindingKeys.add(dedupeKey);
73054
+ }
73055
+ }
71828
73056
  }
71829
73057
  return errors;
71830
73058
  }
@@ -71837,7 +73065,9 @@ async function handleValidate(flags, json) {
71837
73065
  valid: errors.length === 0,
71838
73066
  file: filePath,
71839
73067
  errors,
73068
+ groups: Object.keys(yaml.access.groups ?? {}).length,
71840
73069
  roles: Object.keys(yaml.access.roles ?? {}).length,
73070
+ members: Object.values(yaml.access.groups ?? {}).reduce((acc, group) => acc + (group.members?.length ?? 0), 0),
71841
73071
  bindings: (yaml.access.bindings ?? []).length
71842
73072
  }, json);
71843
73073
  return;
@@ -71850,28 +73080,124 @@ async function handleValidate(flags, json) {
71850
73080
  }
71851
73081
  process.exit(1);
71852
73082
  }
73083
+ const groupCount = Object.keys(yaml.access.groups ?? {}).length;
71853
73084
  const roleCount = Object.keys(yaml.access.roles ?? {}).length;
73085
+ const memberCount = Object.values(yaml.access.groups ?? {}).reduce((acc, group) => acc + (group.members?.length ?? 0), 0);
71854
73086
  const bindingCount = (yaml.access.bindings ?? []).length;
71855
- console.log(`Valid (${roleCount} roles, ${bindingCount} bindings)`);
73087
+ console.log(`Valid (${groupCount} groups, ${memberCount} members, ${roleCount} roles, ${bindingCount} bindings)`);
71856
73088
  }
71857
- function bindingKey(roleName, principalType, principalId, projectId) {
71858
- const scope = projectId ?? "org";
71859
- return `${roleName}|${principalType}|${principalId}|${scope}`;
73089
+ function resolveGroupPrincipalId(groupRef, groupsById, groupsBySlug) {
73090
+ if (groupsById.has(groupRef)) {
73091
+ return groupRef;
73092
+ }
73093
+ return groupsBySlug.get(groupRef)?.id ?? null;
71860
73094
  }
71861
73095
  async function computePlan(yaml, orgId, context2) {
71862
- const [apiRolesResponse, apiBindingsResponse] = await Promise.all([
73096
+ const [apiGroupsResponse, apiRolesResponse, apiBindingsResponse] = await Promise.all([
73097
+ requestJson(context2, `/orgs/${orgId}/access/groups`),
71863
73098
  requestJson(context2, `/orgs/${orgId}/access/roles`),
71864
73099
  requestJson(context2, `/orgs/${orgId}/access/bindings`)
71865
73100
  ]);
73101
+ const apiGroups = unwrapListResponse(apiGroupsResponse);
71866
73102
  const apiRoles = unwrapListResponse(apiRolesResponse);
71867
73103
  const apiBindings = unwrapListResponse(apiBindingsResponse);
73104
+ const groupMemberEntries = await Promise.all(
73105
+ apiGroups.map(async (group) => {
73106
+ const membersResponse = await requestJson(
73107
+ context2,
73108
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group.id)}/members`
73109
+ );
73110
+ return [group.id, unwrapListResponse(membersResponse)];
73111
+ })
73112
+ );
73113
+ const membersByGroupId = new Map(
73114
+ groupMemberEntries.map(([groupId, members]) => [groupId, members])
73115
+ );
73116
+ const groupsBySlug = new Map(apiGroups.map((group) => [group.slug, group]));
73117
+ const groupsById = new Map(apiGroups.map((group) => [group.id, group]));
71868
73118
  const rolesByName = new Map(apiRoles.map((r) => [r.name, r]));
71869
73119
  const plan = {
73120
+ groups: [],
73121
+ group_members: [],
71870
73122
  roles: [],
71871
73123
  bindings: [],
73124
+ unchanged_groups: 0,
73125
+ unchanged_group_members: 0,
71872
73126
  unchanged_roles: 0,
71873
73127
  unchanged_bindings: 0
71874
73128
  };
73129
+ const declaredGroupSlugs = /* @__PURE__ */ new Set();
73130
+ for (const [slug, declaredGroup] of Object.entries(yaml.access.groups ?? {})) {
73131
+ declaredGroupSlugs.add(slug);
73132
+ const existing = groupsBySlug.get(slug);
73133
+ const desiredMembers = declaredGroup.members ?? [];
73134
+ if (!existing) {
73135
+ plan.groups.push({ action: "create", slug, group: declaredGroup });
73136
+ for (const member of desiredMembers) {
73137
+ plan.group_members.push({
73138
+ action: "add",
73139
+ groupSlug: slug,
73140
+ member
73141
+ });
73142
+ }
73143
+ continue;
73144
+ }
73145
+ const changes = [];
73146
+ const desiredName = declaredGroup.name ?? groupDefaultName(slug);
73147
+ const desiredDescription = declaredGroup.description ?? null;
73148
+ if (desiredName !== existing.name) {
73149
+ changes.push(`name: "${existing.name}" -> "${desiredName}"`);
73150
+ }
73151
+ if (desiredDescription !== (existing.description ?? null)) {
73152
+ changes.push(`description: "${existing.description ?? "(none)"}" -> "${desiredDescription ?? "(none)"}"`);
73153
+ }
73154
+ if (changes.length > 0) {
73155
+ plan.groups.push({
73156
+ action: "update",
73157
+ slug,
73158
+ id: existing.id,
73159
+ group: declaredGroup,
73160
+ changes
73161
+ });
73162
+ } else {
73163
+ plan.unchanged_groups++;
73164
+ }
73165
+ const existingMembers = membersByGroupId.get(existing.id) ?? [];
73166
+ const existingMemberKeys = new Set(existingMembers.map(apiMemberKey));
73167
+ const desiredMemberKeys = new Set(desiredMembers.map(declaredMemberKey));
73168
+ for (const member of desiredMembers) {
73169
+ const key = declaredMemberKey(member);
73170
+ if (existingMemberKeys.has(key)) {
73171
+ plan.unchanged_group_members++;
73172
+ } else {
73173
+ plan.group_members.push({
73174
+ action: "add",
73175
+ groupSlug: slug,
73176
+ member
73177
+ });
73178
+ }
73179
+ }
73180
+ for (const member of existingMembers) {
73181
+ const key = apiMemberKey(member);
73182
+ if (!desiredMemberKeys.has(key)) {
73183
+ plan.group_members.push({
73184
+ action: "remove",
73185
+ groupSlug: slug,
73186
+ groupId: existing.id,
73187
+ member
73188
+ });
73189
+ }
73190
+ }
73191
+ }
73192
+ for (const apiGroup of apiGroups) {
73193
+ if (!declaredGroupSlugs.has(apiGroup.slug)) {
73194
+ plan.groups.push({
73195
+ action: "prune",
73196
+ slug: apiGroup.slug,
73197
+ id: apiGroup.id
73198
+ });
73199
+ }
73200
+ }
71875
73201
  const declaredRoleNames = /* @__PURE__ */ new Set();
71876
73202
  for (const [name, declaredRole] of Object.entries(yaml.access.roles ?? {})) {
71877
73203
  declaredRoleNames.add(name);
@@ -71908,49 +73234,146 @@ async function computePlan(yaml, orgId, context2) {
71908
73234
  plan.roles.push({ action: "prune", name: apiRole.name, id: apiRole.id });
71909
73235
  }
71910
73236
  }
71911
- const existingBindingKeys = /* @__PURE__ */ new Map();
73237
+ const existingBindingByMatchKey = /* @__PURE__ */ new Map();
73238
+ const existingBindingByIdentityKey = /* @__PURE__ */ new Map();
71912
73239
  for (const ab of apiBindings) {
71913
- const key = bindingKey(ab.role_name, ab.principal_type, ab.principal_id, ab.project_id);
71914
- existingBindingKeys.set(key, ab);
73240
+ const identityKey = bindingIdentityKey(ab.role_name, ab.principal_type, ab.principal_id, ab.project_id);
73241
+ existingBindingByIdentityKey.set(identityKey, ab);
73242
+ const matchKey = bindingMatchKey(
73243
+ ab.role_name,
73244
+ ab.principal_type,
73245
+ ab.principal_id,
73246
+ ab.project_id,
73247
+ ab.scope_json
73248
+ );
73249
+ existingBindingByMatchKey.set(matchKey, ab);
71915
73250
  }
71916
- const matchedApiBindingKeys = /* @__PURE__ */ new Set();
73251
+ const matchedApiBindingIds = /* @__PURE__ */ new Set();
73252
+ const groupsMarkedForPrune = new Set(
73253
+ plan.groups.filter((group) => group.action === "prune").map((group) => group.id)
73254
+ );
71917
73255
  for (const declaredBinding of yaml.access.bindings ?? []) {
71918
- for (const roleName of declaredBinding.roles) {
71919
- const projectId = declaredBinding.scope === "project" ? declaredBinding.project_id : void 0;
71920
- const key = bindingKey(
73256
+ const normalizedDeclaredScope = normalizeBindingScope(declaredBinding.scope);
73257
+ const principalType = declaredBinding.subject.type;
73258
+ let principalIdHint = declaredBinding.subject.id;
73259
+ if (principalType === "group") {
73260
+ const resolvedGroupId = resolveGroupPrincipalId(declaredBinding.subject.id, groupsById, groupsBySlug);
73261
+ if (resolvedGroupId) {
73262
+ principalIdHint = resolvedGroupId;
73263
+ }
73264
+ }
73265
+ for (const roleName of [...new Set(declaredBinding.roles)]) {
73266
+ const identityKey = bindingIdentityKey(
71921
73267
  roleName,
71922
- declaredBinding.subject.type,
71923
- declaredBinding.subject.id,
71924
- projectId ?? null
73268
+ principalType,
73269
+ principalIdHint,
73270
+ declaredBinding.project_id ?? null
71925
73271
  );
71926
- if (existingBindingKeys.has(key)) {
71927
- matchedApiBindingKeys.add(key);
73272
+ const matchKey = bindingMatchKey(
73273
+ roleName,
73274
+ principalType,
73275
+ principalIdHint,
73276
+ declaredBinding.project_id ?? null,
73277
+ normalizedDeclaredScope
73278
+ );
73279
+ const exactMatch = existingBindingByMatchKey.get(matchKey);
73280
+ if (exactMatch) {
73281
+ matchedApiBindingIds.add(exactMatch.id);
71928
73282
  plan.unchanged_bindings++;
71929
- } else {
71930
- plan.bindings.push({ action: "create", binding: declaredBinding, roleName });
73283
+ continue;
71931
73284
  }
73285
+ const identityMatch = existingBindingByIdentityKey.get(identityKey);
73286
+ if (identityMatch) {
73287
+ matchedApiBindingIds.add(identityMatch.id);
73288
+ const changes = [];
73289
+ if (!scopeEquals(identityMatch.scope_json, normalizedDeclaredScope)) {
73290
+ changes.push(
73291
+ `scope: ${formatBindingScope(identityMatch.scope_json)} -> ${formatBindingScope(normalizedDeclaredScope)}`
73292
+ );
73293
+ }
73294
+ plan.bindings.push({
73295
+ action: "replace",
73296
+ existing: identityMatch,
73297
+ binding: declaredBinding,
73298
+ roleName,
73299
+ principalIdHint,
73300
+ changes
73301
+ });
73302
+ continue;
73303
+ }
73304
+ plan.bindings.push({
73305
+ action: "create",
73306
+ binding: declaredBinding,
73307
+ roleName,
73308
+ principalIdHint
73309
+ });
71932
73310
  }
71933
73311
  }
71934
- for (const [key, ab] of existingBindingKeys) {
71935
- if (!matchedApiBindingKeys.has(key)) {
71936
- plan.bindings.push({ action: "prune", binding: ab });
73312
+ for (const ab of apiBindings) {
73313
+ if (matchedApiBindingIds.has(ab.id)) {
73314
+ continue;
71937
73315
  }
73316
+ if (ab.principal_type === "group" && groupsMarkedForPrune.has(ab.principal_id)) {
73317
+ continue;
73318
+ }
73319
+ plan.bindings.push({ action: "prune", binding: ab });
71938
73320
  }
71939
73321
  return plan;
71940
73322
  }
71941
73323
  function hasChanges(plan, prune) {
73324
+ const groupChanges = plan.groups.filter(
73325
+ (g) => g.action === "create" || g.action === "update" || g.action === "prune" && prune
73326
+ );
73327
+ const memberChanges = plan.group_members.filter(
73328
+ (m) => m.action === "add" || m.action === "remove"
73329
+ );
71942
73330
  const roleChanges = plan.roles.filter(
71943
73331
  (r) => r.action === "create" || r.action === "update" || r.action === "prune" && prune
71944
73332
  );
71945
73333
  const bindingChanges = plan.bindings.filter(
71946
- (b) => b.action === "create" || b.action === "prune" && prune
73334
+ (b) => b.action === "create" || b.action === "replace" || b.action === "prune" && prune
71947
73335
  );
71948
- return roleChanges.length > 0 || bindingChanges.length > 0;
73336
+ return groupChanges.length > 0 || memberChanges.length > 0 || roleChanges.length > 0 || bindingChanges.length > 0;
71949
73337
  }
71950
73338
  function printPlan(plan, orgId, prune) {
71951
73339
  console.log(`
71952
73340
  Access Plan for ${orgId}:
71953
73341
  `);
73342
+ const groupCreates = plan.groups.filter((g) => g.action === "create");
73343
+ const groupUpdates = plan.groups.filter((g) => g.action === "update");
73344
+ const groupPrunes = plan.groups.filter((g) => g.action === "prune");
73345
+ if (groupCreates.length > 0 || groupUpdates.length > 0 || groupPrunes.length > 0) {
73346
+ console.log("Groups:");
73347
+ for (const group of groupCreates) {
73348
+ const name = group.group.name ?? groupDefaultName(group.slug);
73349
+ console.log(` + CREATE ${group.slug} (${name})`);
73350
+ }
73351
+ for (const group of groupUpdates) {
73352
+ console.log(` ~ UPDATE ${group.slug}: ${group.changes.join("; ")}`);
73353
+ }
73354
+ if (groupPrunes.length > 0) {
73355
+ if (prune) {
73356
+ for (const group of groupPrunes) {
73357
+ console.log(` - DELETE ${group.slug} (${group.id})`);
73358
+ }
73359
+ } else {
73360
+ console.log(` ? Undeclared (not pruned): ${groupPrunes.map((group) => group.slug).join(", ")}`);
73361
+ }
73362
+ }
73363
+ console.log("");
73364
+ }
73365
+ const memberAdds = plan.group_members.filter((m) => m.action === "add");
73366
+ const memberRemoves = plan.group_members.filter((m) => m.action === "remove");
73367
+ if (memberAdds.length > 0 || memberRemoves.length > 0) {
73368
+ console.log("Group Memberships:");
73369
+ for (const member of memberAdds) {
73370
+ console.log(` + ADD ${member.member.type}:${member.member.id} -> ${member.groupSlug}`);
73371
+ }
73372
+ for (const member of memberRemoves) {
73373
+ console.log(` - REMOVE ${member.member.principal_type}:${member.member.principal_id} -> ${member.groupSlug}`);
73374
+ }
73375
+ console.log("");
73376
+ }
71954
73377
  const roleCreates = plan.roles.filter((r) => r.action === "create");
71955
73378
  const roleUpdates = plan.roles.filter((r) => r.action === "update");
71956
73379
  const rolePrunes = plan.roles.filter((r) => r.action === "prune");
@@ -71974,12 +73397,21 @@ Access Plan for ${orgId}:
71974
73397
  console.log("");
71975
73398
  }
71976
73399
  const bindingCreates = plan.bindings.filter((b) => b.action === "create");
73400
+ const bindingReplaces = plan.bindings.filter((b) => b.action === "replace");
71977
73401
  const bindingPrunes = plan.bindings.filter((b) => b.action === "prune");
71978
- if (bindingCreates.length > 0 || bindingPrunes.length > 0) {
73402
+ if (bindingCreates.length > 0 || bindingReplaces.length > 0 || bindingPrunes.length > 0) {
71979
73403
  console.log("Bindings:");
71980
73404
  for (const b of bindingCreates) {
71981
- const scopeLabel = b.binding.scope === "project" ? `project: ${b.binding.project_id}` : "org-wide";
71982
- console.log(` + BIND ${b.roleName} -> ${b.binding.subject.type}:${b.binding.subject.id} (${scopeLabel})`);
73405
+ const scopeLabel = b.binding.project_id ? `project: ${b.binding.project_id}` : "org-wide";
73406
+ console.log(
73407
+ ` + BIND ${b.roleName} -> ${b.binding.subject.type}:${b.binding.subject.id} (${scopeLabel}, scope=${formatBindingScope(b.binding.scope)})`
73408
+ );
73409
+ }
73410
+ for (const b of bindingReplaces) {
73411
+ const scopeLabel = b.binding.project_id ? `project: ${b.binding.project_id}` : "org-wide";
73412
+ console.log(
73413
+ ` ~ REPLACE ${b.roleName} -> ${b.binding.subject.type}:${b.binding.subject.id} (${scopeLabel}): ${b.changes.join("; ")}`
73414
+ );
71983
73415
  }
71984
73416
  if (bindingPrunes.length > 0) {
71985
73417
  if (prune) {
@@ -71993,9 +73425,11 @@ Access Plan for ${orgId}:
71993
73425
  }
71994
73426
  console.log("");
71995
73427
  }
71996
- const totalUnchanged = plan.unchanged_roles + plan.unchanged_bindings;
73428
+ const totalUnchanged = plan.unchanged_groups + plan.unchanged_group_members + plan.unchanged_roles + plan.unchanged_bindings;
71997
73429
  if (totalUnchanged > 0) {
71998
- console.log(`Unchanged: ${plan.unchanged_roles} role(s), ${plan.unchanged_bindings} binding(s)`);
73430
+ console.log(
73431
+ `Unchanged: ${plan.unchanged_groups} group(s), ${plan.unchanged_group_members} member(s), ${plan.unchanged_roles} role(s), ${plan.unchanged_bindings} binding(s)`
73432
+ );
71999
73433
  }
72000
73434
  if (!hasChanges(plan, prune)) {
72001
73435
  console.log("No changes needed.");
@@ -72004,6 +73438,52 @@ Access Plan for ${orgId}:
72004
73438
  function planToJson(plan, orgId) {
72005
73439
  return {
72006
73440
  org_id: orgId,
73441
+ groups: {
73442
+ create: plan.groups.filter((group) => group.action === "create").map((group) => {
73443
+ const create = group;
73444
+ return {
73445
+ slug: create.slug,
73446
+ name: create.group.name ?? groupDefaultName(create.slug),
73447
+ description: create.group.description ?? null
73448
+ };
73449
+ }),
73450
+ update: plan.groups.filter((group) => group.action === "update").map((group) => {
73451
+ const update = group;
73452
+ return {
73453
+ slug: update.slug,
73454
+ id: update.id,
73455
+ changes: update.changes
73456
+ };
73457
+ }),
73458
+ prune: plan.groups.filter((group) => group.action === "prune").map((group) => {
73459
+ const prune = group;
73460
+ return {
73461
+ slug: prune.slug,
73462
+ id: prune.id
73463
+ };
73464
+ }),
73465
+ unchanged: plan.unchanged_groups
73466
+ },
73467
+ group_members: {
73468
+ add: plan.group_members.filter((member) => member.action === "add").map((member) => {
73469
+ const add = member;
73470
+ return {
73471
+ group_slug: add.groupSlug,
73472
+ principal_type: add.member.type,
73473
+ principal_id: add.member.id
73474
+ };
73475
+ }),
73476
+ remove: plan.group_members.filter((member) => member.action === "remove").map((member) => {
73477
+ const remove = member;
73478
+ return {
73479
+ group_slug: remove.groupSlug,
73480
+ group_id: remove.groupId,
73481
+ principal_type: remove.member.principal_type,
73482
+ principal_id: remove.member.principal_id
73483
+ };
73484
+ }),
73485
+ unchanged: plan.unchanged_group_members
73486
+ },
72007
73487
  roles: {
72008
73488
  create: plan.roles.filter((r) => r.action === "create").map((r) => {
72009
73489
  const cr = r;
@@ -72026,10 +73506,22 @@ function planToJson(plan, orgId) {
72026
73506
  role: cb.roleName,
72027
73507
  subject_type: cb.binding.subject.type,
72028
73508
  subject_id: cb.binding.subject.id,
72029
- scope: cb.binding.scope,
73509
+ scope: normalizeBindingScope(cb.binding.scope) ?? null,
72030
73510
  project_id: cb.binding.project_id
72031
73511
  };
72032
73512
  }),
73513
+ replace: plan.bindings.filter((b) => b.action === "replace").map((b) => {
73514
+ const rb = b;
73515
+ return {
73516
+ id: rb.existing.id,
73517
+ role: rb.roleName,
73518
+ subject_type: rb.binding.subject.type,
73519
+ subject_id: rb.binding.subject.id,
73520
+ scope: normalizeBindingScope(rb.binding.scope) ?? null,
73521
+ project_id: rb.binding.project_id,
73522
+ changes: rb.changes
73523
+ };
73524
+ }),
72033
73525
  prune: plan.bindings.filter((b) => b.action === "prune").map((b) => {
72034
73526
  const pb = b;
72035
73527
  return {
@@ -72064,6 +73556,26 @@ ${lines.join("\n")}`);
72064
73556
  }
72065
73557
  printPlan(plan, orgId, false);
72066
73558
  }
73559
+ async function fetchOrgGroups(orgId, context2) {
73560
+ const groupsResponse = await requestJson(
73561
+ context2,
73562
+ `/orgs/${orgId}/access/groups`
73563
+ );
73564
+ return unwrapListResponse(groupsResponse);
73565
+ }
73566
+ function resolveBindingPrincipalIdForApply(principalType, principalIdHint, groupIdsBySlug, groupIds) {
73567
+ if (principalType !== "group") {
73568
+ return principalIdHint;
73569
+ }
73570
+ if (groupIds.has(principalIdHint)) {
73571
+ return principalIdHint;
73572
+ }
73573
+ const fromSlug = groupIdsBySlug.get(principalIdHint);
73574
+ if (fromSlug) {
73575
+ return fromSlug;
73576
+ }
73577
+ throw new Error(`Group '${principalIdHint}' does not exist in the target org`);
73578
+ }
72067
73579
  async function handleSync(flags, context2, json) {
72068
73580
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
72069
73581
  if (!orgId) {
@@ -72103,10 +73615,16 @@ ${lines.join("\n")}`);
72103
73615
  }
72104
73616
  }
72105
73617
  const applied = {
73618
+ groups_created: 0,
73619
+ groups_updated: 0,
73620
+ groups_deleted: 0,
73621
+ group_members_added: 0,
73622
+ group_members_removed: 0,
72106
73623
  roles_created: 0,
72107
73624
  roles_updated: 0,
72108
73625
  roles_deleted: 0,
72109
73626
  bindings_created: 0,
73627
+ bindings_replaced: 0,
72110
73628
  bindings_deleted: 0
72111
73629
  };
72112
73630
  for (const ra of plan.roles) {
@@ -72146,25 +73664,123 @@ ${lines.join("\n")}`);
72146
73664
  if (!json) console.log(` ~ Updated role '${ra.name}'`);
72147
73665
  }
72148
73666
  }
72149
- for (const ba of plan.bindings) {
72150
- if (ba.action === "create") {
72151
- const body = {
72152
- role_name: ba.roleName,
72153
- principal_type: ba.binding.subject.type,
72154
- principal_id: ba.binding.subject.id
73667
+ for (const ga of plan.groups) {
73668
+ if (ga.action === "create") {
73669
+ const payload = {
73670
+ name: ga.group.name ?? groupDefaultName(ga.slug),
73671
+ slug: ga.slug,
73672
+ description: ga.group.description
73673
+ };
73674
+ await requestJson(
73675
+ context2,
73676
+ `/orgs/${orgId}/access/groups`,
73677
+ {
73678
+ method: "POST",
73679
+ body: payload
73680
+ }
73681
+ );
73682
+ applied.groups_created++;
73683
+ if (!json) console.log(` + Created group '${ga.slug}'`);
73684
+ continue;
73685
+ }
73686
+ if (ga.action === "update") {
73687
+ const payload = {
73688
+ name: ga.group.name ?? groupDefaultName(ga.slug),
73689
+ description: ga.group.description ?? null
72155
73690
  };
72156
- if (ba.binding.scope === "project" && ba.binding.project_id) {
72157
- body.project_id = ba.binding.project_id;
73691
+ await requestJson(
73692
+ context2,
73693
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(ga.id)}`,
73694
+ {
73695
+ method: "PATCH",
73696
+ body: payload
73697
+ }
73698
+ );
73699
+ applied.groups_updated++;
73700
+ if (!json) console.log(` ~ Updated group '${ga.slug}'`);
73701
+ }
73702
+ }
73703
+ const groupsAfterCreateUpdate = await fetchOrgGroups(orgId, context2);
73704
+ const groupIdsBySlug = new Map(groupsAfterCreateUpdate.map((group) => [group.slug, group.id]));
73705
+ const groupIds = new Set(groupsAfterCreateUpdate.map((group) => group.id));
73706
+ for (const ma of plan.group_members) {
73707
+ if (ma.action === "add") {
73708
+ const groupId2 = groupIdsBySlug.get(ma.groupSlug);
73709
+ if (!groupId2) {
73710
+ throw new Error(`Cannot add member: group '${ma.groupSlug}' does not exist in org '${orgId}'`);
72158
73711
  }
72159
73712
  await requestJson(
72160
73713
  context2,
72161
- `/orgs/${orgId}/access/bindings`,
72162
- { method: "POST", body }
73714
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(groupId2)}/members`,
73715
+ {
73716
+ method: "POST",
73717
+ body: {
73718
+ principal_type: ma.member.type,
73719
+ principal_id: ma.member.id
73720
+ }
73721
+ }
73722
+ );
73723
+ applied.group_members_added++;
73724
+ if (!json) console.log(` + Added ${ma.member.type}:${ma.member.id} to group '${ma.groupSlug}'`);
73725
+ continue;
73726
+ }
73727
+ const groupId = groupIdsBySlug.get(ma.groupSlug) ?? ma.groupId;
73728
+ await requestJson(
73729
+ context2,
73730
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(groupId)}/members/${ma.member.principal_type}/${encodeURIComponent(ma.member.principal_id)}`,
73731
+ { method: "DELETE" }
73732
+ );
73733
+ applied.group_members_removed++;
73734
+ if (!json) console.log(` - Removed ${ma.member.principal_type}:${ma.member.principal_id} from group '${ma.groupSlug}'`);
73735
+ }
73736
+ for (const ba of plan.bindings) {
73737
+ if (ba.action !== "create" && ba.action !== "replace") {
73738
+ continue;
73739
+ }
73740
+ const principalId = resolveBindingPrincipalIdForApply(
73741
+ ba.binding.subject.type,
73742
+ ba.principalIdHint,
73743
+ groupIdsBySlug,
73744
+ groupIds
73745
+ );
73746
+ if (ba.action === "replace") {
73747
+ await requestJson(
73748
+ context2,
73749
+ `/orgs/${orgId}/access/bindings/${ba.existing.id}`,
73750
+ { method: "DELETE" }
72163
73751
  );
73752
+ }
73753
+ const body = {
73754
+ role_name: ba.roleName,
73755
+ principal_type: ba.binding.subject.type,
73756
+ principal_id: principalId
73757
+ };
73758
+ if (ba.binding.project_id) {
73759
+ body.project_id = ba.binding.project_id;
73760
+ }
73761
+ const normalizedScope = normalizeBindingScope(ba.binding.scope);
73762
+ if (normalizedScope) {
73763
+ body.scope_json = normalizedScope;
73764
+ }
73765
+ await requestJson(
73766
+ context2,
73767
+ `/orgs/${orgId}/access/bindings`,
73768
+ { method: "POST", body }
73769
+ );
73770
+ const scopeLabel = ba.binding.project_id ? `project: ${ba.binding.project_id}` : "org-wide";
73771
+ if (ba.action === "create") {
72164
73772
  applied.bindings_created++;
72165
73773
  if (!json) {
72166
- const scopeLabel = ba.binding.scope === "project" ? `project: ${ba.binding.project_id}` : "org-wide";
72167
- console.log(` + Bound ${ba.roleName} -> ${ba.binding.subject.type}:${ba.binding.subject.id} (${scopeLabel})`);
73774
+ console.log(
73775
+ ` + Bound ${ba.roleName} -> ${ba.binding.subject.type}:${principalId} (${scopeLabel}, scope=${formatBindingScope(ba.binding.scope)})`
73776
+ );
73777
+ }
73778
+ } else {
73779
+ applied.bindings_replaced++;
73780
+ if (!json) {
73781
+ console.log(
73782
+ ` ~ Rebound ${ba.roleName} -> ${ba.binding.subject.type}:${principalId} (${scopeLabel}, scope=${formatBindingScope(ba.binding.scope)})`
73783
+ );
72168
73784
  }
72169
73785
  }
72170
73786
  }
@@ -72183,6 +73799,17 @@ ${lines.join("\n")}`);
72183
73799
  }
72184
73800
  }
72185
73801
  }
73802
+ for (const ga of plan.groups) {
73803
+ if (ga.action === "prune") {
73804
+ await requestJson(
73805
+ context2,
73806
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(ga.id)}`,
73807
+ { method: "DELETE" }
73808
+ );
73809
+ applied.groups_deleted++;
73810
+ if (!json) console.log(` - Deleted group '${ga.slug}'`);
73811
+ }
73812
+ }
72186
73813
  for (const ra of plan.roles) {
72187
73814
  if (ra.action === "prune") {
72188
73815
  await requestJson(
@@ -72200,18 +73827,24 @@ ${lines.join("\n")}`);
72200
73827
  return;
72201
73828
  }
72202
73829
  const parts = [];
73830
+ if (applied.groups_created > 0) parts.push(`${applied.groups_created} group(s) created`);
73831
+ if (applied.groups_updated > 0) parts.push(`${applied.groups_updated} group(s) updated`);
73832
+ if (applied.groups_deleted > 0) parts.push(`${applied.groups_deleted} group(s) deleted`);
73833
+ if (applied.group_members_added > 0) parts.push(`${applied.group_members_added} group member(s) added`);
73834
+ if (applied.group_members_removed > 0) parts.push(`${applied.group_members_removed} group member(s) removed`);
72203
73835
  if (applied.roles_created > 0) parts.push(`${applied.roles_created} role(s) created`);
72204
73836
  if (applied.roles_updated > 0) parts.push(`${applied.roles_updated} role(s) updated`);
72205
73837
  if (applied.roles_deleted > 0) parts.push(`${applied.roles_deleted} role(s) deleted`);
72206
73838
  if (applied.bindings_created > 0) parts.push(`${applied.bindings_created} binding(s) created`);
73839
+ if (applied.bindings_replaced > 0) parts.push(`${applied.bindings_replaced} binding(s) replaced`);
72207
73840
  if (applied.bindings_deleted > 0) parts.push(`${applied.bindings_deleted} binding(s) deleted`);
72208
73841
  console.log(`
72209
73842
  Sync complete: ${parts.join(", ")}`);
72210
73843
  }
72211
73844
 
72212
73845
  // src/commands/docs.ts
72213
- var import_node_fs15 = require("node:fs");
72214
- var import_node_path15 = require("node:path");
73846
+ var import_node_fs16 = require("node:fs");
73847
+ var import_node_path16 = require("node:path");
72215
73848
  function encodeDocPathParam(path6) {
72216
73849
  const trimmed = path6.startsWith("/") ? path6.slice(1) : path6;
72217
73850
  return encodeURIComponent(trimmed);
@@ -72270,9 +73903,9 @@ async function handleDocs(subcommand, positionals, flags, context2) {
72270
73903
  const filePath = getStringFlag(flags, ["file"]);
72271
73904
  const useStdin = flags.stdin === true || flags.stdin === "true";
72272
73905
  if (filePath) {
72273
- content = (0, import_node_fs15.readFileSync)((0, import_node_path15.resolve)(filePath), "utf-8");
73906
+ content = (0, import_node_fs16.readFileSync)((0, import_node_path16.resolve)(filePath), "utf-8");
72274
73907
  } else if (useStdin) {
72275
- content = (0, import_node_fs15.readFileSync)(0, "utf-8");
73908
+ content = (0, import_node_fs16.readFileSync)(0, "utf-8");
72276
73909
  } else {
72277
73910
  throw new Error("Provide --file <path> or --stdin to supply document content");
72278
73911
  }
@@ -72721,6 +74354,9 @@ function formatDuration2(seconds) {
72721
74354
  function pad(label, value, width = 12) {
72722
74355
  return ` ${label.padEnd(width)}${value}`;
72723
74356
  }
74357
+ function parseAvgDurationSeconds(stats) {
74358
+ return stats.avg_duration_seconds ?? stats.avg_duration_s ?? 0;
74359
+ }
72724
74360
  async function analyticsSummary(flags, context2, orgId) {
72725
74361
  const json = Boolean(flags.json);
72726
74362
  const window2 = getStringFlag(flags, ["window"]) ?? "7d";
@@ -72747,7 +74383,7 @@ async function analyticsSummary(flags, context2, orgId) {
72747
74383
  console.log("Pipelines");
72748
74384
  console.log(pad("Runs:", pipelines.runs));
72749
74385
  console.log(pad("Success Rate:", `${pipelines.success_rate.toFixed(1)}%`));
72750
- console.log(pad("Avg Duration:", formatDuration2(pipelines.avg_duration_seconds)));
74386
+ console.log(pad("Avg Duration:", formatDuration2(parseAvgDurationSeconds(pipelines))));
72751
74387
  console.log("");
72752
74388
  console.log("Environments");
72753
74389
  console.log(pad("Total:", environments.total));
@@ -72766,17 +74402,15 @@ async function analyticsJobs(flags, context2, orgId) {
72766
74402
  outputJson(data, true);
72767
74403
  return;
72768
74404
  }
72769
- console.log(`Job Analytics (${data.window || window2} window)`);
74405
+ console.log(`Job Analytics (${window2} window)`);
72770
74406
  console.log("\u2550".repeat(36));
72771
- if (data.jobs.length === 0) {
72772
- console.log(" No jobs in this window.");
72773
- return;
72774
- }
72775
- for (const job of data.jobs) {
72776
- const duration = job.duration_seconds != null ? ` (${formatDuration2(job.duration_seconds)})` : "";
72777
- const desc = job.description ? ` ${job.description}` : "";
72778
- console.log(` ${job.id} ${job.phase}${duration}${desc}`);
72779
- }
74407
+ console.log(pad("As of:", data.as_of));
74408
+ console.log("");
74409
+ console.log("Jobs");
74410
+ console.log(pad("Created:", data.created));
74411
+ console.log(pad("Completed:", data.completed));
74412
+ console.log(pad("Failed:", data.failed));
74413
+ console.log(pad("Active:", data.active));
72780
74414
  }
72781
74415
  async function analyticsPipelines(flags, context2, orgId) {
72782
74416
  const json = Boolean(flags.json);
@@ -72789,22 +74423,14 @@ async function analyticsPipelines(flags, context2, orgId) {
72789
74423
  outputJson(data, true);
72790
74424
  return;
72791
74425
  }
72792
- console.log(`Pipeline Analytics (${data.window || window2} window)`);
74426
+ console.log(`Pipeline Analytics (${window2} window)`);
72793
74427
  console.log("\u2550".repeat(36));
72794
- if (data.pipelines.length === 0) {
72795
- console.log(" No pipeline runs in this window.");
72796
- return;
72797
- }
72798
- for (const p of data.pipelines) {
72799
- console.log(` ${p.name}`);
72800
- console.log(pad("Runs:", p.runs, 16));
72801
- console.log(pad("Success Rate:", `${p.success_rate.toFixed(1)}%`, 16));
72802
- console.log(pad("Avg Duration:", formatDuration2(p.avg_duration_seconds), 16));
72803
- if (p.last_run_at) {
72804
- console.log(pad("Last Run:", p.last_run_at, 16));
72805
- }
72806
- console.log("");
72807
- }
74428
+ console.log(pad("As of:", data.as_of));
74429
+ console.log("");
74430
+ console.log("Pipelines");
74431
+ console.log(pad("Runs:", data.runs));
74432
+ console.log(pad("Success Rate:", `${data.success_rate.toFixed(1)}%`));
74433
+ console.log(pad("Avg Duration:", formatDuration2(parseAvgDurationSeconds(data))));
72808
74434
  }
72809
74435
  async function analyticsEnvHealth(flags, context2, orgId) {
72810
74436
  const json = Boolean(flags.json);
@@ -72818,15 +74444,12 @@ async function analyticsEnvHealth(flags, context2, orgId) {
72818
74444
  }
72819
74445
  console.log("Environment Health");
72820
74446
  console.log("\u2550".repeat(36));
72821
- if (data.environments.length === 0) {
72822
- console.log(" No environments found.");
72823
- return;
72824
- }
72825
- for (const env of data.environments) {
72826
- const lastDeploy = env.last_deploy_at ? ` last deploy: ${env.last_deploy_at}` : "";
72827
- const pods = env.pod_count != null ? ` pods: ${env.pod_count}` : "";
72828
- console.log(` ${env.name} [${env.status}]${pods}${lastDeploy}`);
72829
- }
74447
+ console.log("");
74448
+ console.log(pad("As of:", data.as_of));
74449
+ console.log(pad("Total:", data.total));
74450
+ console.log(pad("Healthy:", data.healthy));
74451
+ console.log(pad("Degraded:", data.degraded));
74452
+ console.log(pad("Unknown:", data.unknown));
72830
74453
  }
72831
74454
  async function handleAnalytics(subcommand, _positionals, flags, context2) {
72832
74455
  const orgId = getStringFlag(flags, ["org", "org-id", "org_id"]) ?? context2.orgId;
@@ -72844,7 +74467,7 @@ async function handleAnalytics(subcommand, _positionals, flags, context2) {
72844
74467
  return analyticsEnvHealth(flags, context2, orgId);
72845
74468
  default:
72846
74469
  throw new Error(
72847
- "Usage: eve analytics <summary|jobs|pipelines|env-health>\n\n summary Org-wide activity summary\n jobs Job breakdown for the window\n pipelines Pipeline success rates and durations\n env-health Current environment health snapshot"
74470
+ "Usage: eve analytics <summary|jobs|pipelines|env-health>\n\n summary Org-wide activity summary\n jobs Job counters for the window\n pipelines Pipeline success rates and durations\n env-health Current environment health snapshot"
72848
74471
  );
72849
74472
  }
72850
74473
  }
@@ -73217,6 +74840,284 @@ async function handleOllama(subcommand, positionals, flags, context2) {
73217
74840
  }
73218
74841
  }
73219
74842
 
74843
+ // src/commands/fs.ts
74844
+ var import_promises = require("node:fs/promises");
74845
+ var import_node_fs17 = require("node:fs");
74846
+ var import_node_os5 = require("node:os");
74847
+ function getOrgOrThrow(flags, context2) {
74848
+ const orgId = getStringFlag(flags, ["org", "org-id", "org_id"]) ?? context2.orgId;
74849
+ if (!orgId) {
74850
+ throw new Error("Missing org id. Provide --org or set a profile default.");
74851
+ }
74852
+ return orgId;
74853
+ }
74854
+ function parseModeFlag(value) {
74855
+ const raw = (value ?? "two-way").trim().toLowerCase();
74856
+ if (raw === "two-way" || raw === "two_way") return "two_way";
74857
+ if (raw === "push-only" || raw === "push_only") return "push_only";
74858
+ if (raw === "pull-only" || raw === "pull_only") return "pull_only";
74859
+ throw new Error(`Invalid mode: ${value}. Use two-way, push-only, or pull-only.`);
74860
+ }
74861
+ function parseStrategyFlag(value) {
74862
+ const raw = (value ?? "").trim().toLowerCase();
74863
+ if (raw === "pick-local" || raw === "pick_local") return "pick_local";
74864
+ if (raw === "pick-remote" || raw === "pick_remote") return "pick_remote";
74865
+ if (raw === "manual") return "manual";
74866
+ throw new Error(`Invalid strategy: ${value}. Use pick-local, pick-remote, or manual.`);
74867
+ }
74868
+ function parseGlobList(value) {
74869
+ if (!value) return void 0;
74870
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
74871
+ }
74872
+ async function resolveLinkId(context2, orgId, flags) {
74873
+ const explicit = getStringFlag(flags, ["link", "link-id", "link_id"]);
74874
+ const links = await requestJson(context2, `/orgs/${orgId}/fs/links`);
74875
+ if (explicit) {
74876
+ const found = links.data.find((item) => item.id === explicit);
74877
+ if (!found) {
74878
+ throw new Error(`Link not found: ${explicit}`);
74879
+ }
74880
+ return found.id;
74881
+ }
74882
+ if (links.data.length === 0) {
74883
+ throw new Error("No sync links found. Run: eve fs sync init --org <org> --local <path>");
74884
+ }
74885
+ return links.data[0].id;
74886
+ }
74887
+ async function streamFsEvents(context2, orgId, afterSeq) {
74888
+ const headers = {
74889
+ Accept: "text/event-stream"
74890
+ };
74891
+ if (context2.token) {
74892
+ headers.Authorization = `Bearer ${context2.token}`;
74893
+ }
74894
+ const response = await fetch(`${context2.apiUrl}/orgs/${orgId}/fs/events/stream?after_seq=${afterSeq}`, {
74895
+ method: "GET",
74896
+ headers
74897
+ });
74898
+ if (!response.ok || !response.body) {
74899
+ const body = await response.text();
74900
+ throw new Error(`HTTP ${response.status}: ${body}`);
74901
+ }
74902
+ const reader = response.body.getReader();
74903
+ const decoder = new TextDecoder();
74904
+ let buffer = "";
74905
+ let currentEvent = "";
74906
+ let currentData = "";
74907
+ while (true) {
74908
+ const { done, value } = await reader.read();
74909
+ if (done) break;
74910
+ buffer += decoder.decode(value, { stream: true });
74911
+ const events = buffer.split("\n\n");
74912
+ buffer = events.pop() ?? "";
74913
+ for (const eventBlock of events) {
74914
+ const lines = eventBlock.split("\n");
74915
+ for (const line of lines) {
74916
+ if (line.startsWith("event:")) {
74917
+ currentEvent = line.slice(6).trim();
74918
+ } else if (line.startsWith("data:")) {
74919
+ currentData += `${line.slice(5).trim()}
74920
+ `;
74921
+ }
74922
+ }
74923
+ if (currentData) {
74924
+ const payload = currentData.trim();
74925
+ if (currentEvent === "fs_event") {
74926
+ try {
74927
+ const parsed = JSON.parse(payload);
74928
+ console.log(`[${parsed.seq}] ${parsed.event_type} ${parsed.path} (${parsed.source_side}) ${parsed.created_at}`);
74929
+ } catch {
74930
+ console.log(payload);
74931
+ }
74932
+ } else if (currentEvent === "error") {
74933
+ console.error(payload);
74934
+ }
74935
+ }
74936
+ currentEvent = "";
74937
+ currentData = "";
74938
+ }
74939
+ }
74940
+ }
74941
+ async function handleSync2(action, positionals, flags, context2) {
74942
+ const json = Boolean(flags.json);
74943
+ const orgId = getOrgOrThrow(flags, context2);
74944
+ switch (action) {
74945
+ case "init": {
74946
+ const localPath = getStringFlag(flags, ["local", "path"]);
74947
+ if (!localPath) {
74948
+ throw new Error("Usage: eve fs sync init --org <org_id> --local <path> [--mode two-way]");
74949
+ }
74950
+ const mode = parseModeFlag(getStringFlag(flags, ["mode"]));
74951
+ const includes = parseGlobList(getStringFlag(flags, ["include", "includes"]));
74952
+ const excludes = parseGlobList(getStringFlag(flags, ["exclude", "excludes"]));
74953
+ const publicKey = getStringFlag(flags, ["public-key", "public_key"]);
74954
+ const enroll = await requestJson(context2, `/orgs/${orgId}/fs/devices/enroll`, {
74955
+ method: "POST",
74956
+ body: {
74957
+ device_name: getStringFlag(flags, ["device-name", "device_name"]) ?? (0, import_node_os5.hostname)(),
74958
+ platform: process.platform,
74959
+ client_version: "dev",
74960
+ ...publicKey ? { public_key: publicKey } : {}
74961
+ }
74962
+ });
74963
+ const link = await requestJson(context2, `/orgs/${orgId}/fs/links`, {
74964
+ method: "POST",
74965
+ body: {
74966
+ device_id: enroll.device.id,
74967
+ mode,
74968
+ local_path: localPath,
74969
+ remote_path: getStringFlag(flags, ["remote-path", "remote_path"]) ?? "/",
74970
+ ...includes ? { includes } : {},
74971
+ ...excludes ? { excludes } : {}
74972
+ }
74973
+ });
74974
+ outputJson({
74975
+ device: enroll.device,
74976
+ enrollment: enroll.enrollment,
74977
+ link: link.link,
74978
+ runtime: link.runtime
74979
+ }, json, `Sync initialized for ${orgId}`);
74980
+ return;
74981
+ }
74982
+ case "status": {
74983
+ const [status, links] = await Promise.all([
74984
+ requestJson(context2, `/orgs/${orgId}/fs/status`),
74985
+ requestJson(context2, `/orgs/${orgId}/fs/links`)
74986
+ ]);
74987
+ if (json) {
74988
+ outputJson({ ...status, links_detail: links.data }, true);
74989
+ return;
74990
+ }
74991
+ console.log(`Org: ${orgId}`);
74992
+ console.log(`Gateway: ${status.gateway.status}`);
74993
+ if (status.gateway.last_heartbeat_at) console.log(`Last heartbeat: ${status.gateway.last_heartbeat_at}`);
74994
+ console.log(`Links: active=${status.links.active} paused=${status.links.paused} revoked=${status.links.revoked}`);
74995
+ console.log(`Latest seq: ${status.events.latest_seq}`);
74996
+ if (links.data.length > 0) {
74997
+ console.log("");
74998
+ for (const link of links.data) {
74999
+ console.log(`${link.id} ${link.mode} ${link.status} cursor=${link.last_cursor} lag_ms=${link.lag_ms ?? "n/a"} backlog=${link.backlog ?? 0}`);
75000
+ }
75001
+ }
75002
+ return;
75003
+ }
75004
+ case "logs": {
75005
+ const afterSeq = Number(getStringFlag(flags, ["after", "after-seq", "after_seq"]) ?? "0");
75006
+ const limit = Number(getStringFlag(flags, ["limit"]) ?? "200");
75007
+ const follow = flags.follow === true || flags.follow === "true";
75008
+ if (follow) {
75009
+ await streamFsEvents(context2, orgId, Number.isFinite(afterSeq) ? afterSeq : 0);
75010
+ return;
75011
+ }
75012
+ const events = await requestJson(
75013
+ context2,
75014
+ `/orgs/${orgId}/fs/events?after_seq=${Number.isFinite(afterSeq) ? afterSeq : 0}&limit=${Number.isFinite(limit) ? limit : 200}`
75015
+ );
75016
+ outputJson(events, json);
75017
+ return;
75018
+ }
75019
+ case "pause":
75020
+ case "resume":
75021
+ case "disconnect": {
75022
+ const linkId = await resolveLinkId(context2, orgId, flags);
75023
+ const nextStatus = action === "pause" ? "paused" : action === "resume" ? "active" : "revoked";
75024
+ const updated = await requestJson(context2, `/orgs/${orgId}/fs/links/${linkId}`, {
75025
+ method: "PATCH",
75026
+ body: { status: nextStatus }
75027
+ });
75028
+ outputJson(updated, json, `Link ${linkId} ${nextStatus}`);
75029
+ return;
75030
+ }
75031
+ case "mode": {
75032
+ const modeValue = getStringFlag(flags, ["set", "mode"]);
75033
+ if (!modeValue) {
75034
+ throw new Error("Usage: eve fs sync mode --org <org_id> --set <two-way|push-only|pull-only>");
75035
+ }
75036
+ const linkId = await resolveLinkId(context2, orgId, flags);
75037
+ const updated = await requestJson(context2, `/orgs/${orgId}/fs/links/${linkId}`, {
75038
+ method: "PATCH",
75039
+ body: { mode: parseModeFlag(modeValue) }
75040
+ });
75041
+ outputJson(updated, json, `Link ${linkId} mode updated`);
75042
+ return;
75043
+ }
75044
+ case "conflicts": {
75045
+ const openOnly = flags["open-only"] === true || flags["open_only"] === true || flags["open-only"] === "true" || flags["open_only"] === "true";
75046
+ const result = await requestJson(
75047
+ context2,
75048
+ `/orgs/${orgId}/fs/conflicts${openOnly ? "?open_only=true" : ""}`
75049
+ );
75050
+ outputJson(result, json);
75051
+ return;
75052
+ }
75053
+ case "resolve": {
75054
+ const conflictId = getStringFlag(flags, ["conflict", "conflict-id", "conflict_id"]) ?? positionals[0];
75055
+ if (!conflictId) {
75056
+ throw new Error("Usage: eve fs sync resolve --org <org_id> --conflict <conflict_id> --strategy <pick-remote|pick-local|manual>");
75057
+ }
75058
+ const strategy = parseStrategyFlag(getStringFlag(flags, ["strategy"]));
75059
+ const mergedContent = getStringFlag(flags, ["merged-content", "merged_content"]);
75060
+ const result = await requestJson(context2, `/orgs/${orgId}/fs/conflicts/${conflictId}/resolve`, {
75061
+ method: "POST",
75062
+ body: {
75063
+ strategy,
75064
+ ...mergedContent ? { merged_content: mergedContent } : {}
75065
+ }
75066
+ });
75067
+ outputJson(result, json, `Conflict ${conflictId} resolved`);
75068
+ return;
75069
+ }
75070
+ case "doctor": {
75071
+ const status = await requestJson(context2, `/orgs/${orgId}/fs/status`);
75072
+ const links = await requestJson(context2, `/orgs/${orgId}/fs/links`);
75073
+ const health = {
75074
+ auth: "ok",
75075
+ gateway_status: status.gateway.status,
75076
+ links: links.data.length,
75077
+ active_links: links.data.filter((item) => item.status === "active").length,
75078
+ cursor_drift: 0,
75079
+ local_path_checks: []
75080
+ };
75081
+ if (links.data.length > 0) {
75082
+ const minCursor = links.data.reduce((min, item) => Math.min(min, item.last_cursor), Number.POSITIVE_INFINITY);
75083
+ const cursorBase = Number.isFinite(minCursor) ? minCursor : 0;
75084
+ health.cursor_drift = Math.max(0, status.events.latest_seq - cursorBase);
75085
+ }
75086
+ for (const link of links.data) {
75087
+ let writable = false;
75088
+ try {
75089
+ await (0, import_promises.access)(link.local_path, import_node_fs17.constants.R_OK | import_node_fs17.constants.W_OK);
75090
+ writable = true;
75091
+ } catch {
75092
+ writable = false;
75093
+ }
75094
+ health.local_path_checks.push({
75095
+ link_id: link.id,
75096
+ local_path: link.local_path,
75097
+ writable
75098
+ });
75099
+ }
75100
+ outputJson(health, json, `FS sync doctor completed for ${orgId}`);
75101
+ return;
75102
+ }
75103
+ default:
75104
+ throw new Error(
75105
+ "Usage: eve fs sync <init|status|logs|pause|resume|disconnect|mode|conflicts|resolve|doctor>\n\n init --org <org> --local <path> [--mode two-way|push-only|pull-only]\n status --org <org>\n logs --org <org> [--after N] [--limit N] [--follow]\n pause --org <org> [--link <link_id>]\n resume --org <org> [--link <link_id>]\n disconnect --org <org> [--link <link_id>]\n mode --org <org> --set <two-way|push-only|pull-only> [--link <link_id>]\n conflicts --org <org> [--open-only]\n resolve --org <org> --conflict <id> --strategy <pick-remote|pick-local|manual>\n doctor --org <org>"
75106
+ );
75107
+ }
75108
+ }
75109
+ async function handleFs(subcommand, positionals, flags, context2) {
75110
+ switch (subcommand) {
75111
+ case "sync": {
75112
+ const action = positionals[0];
75113
+ await handleSync2(action, positionals.slice(1), flags, context2);
75114
+ return;
75115
+ }
75116
+ default:
75117
+ throw new Error("Usage: eve fs sync <init|status|logs|pause|resume|disconnect|mode|conflicts|resolve|doctor>");
75118
+ }
75119
+ }
75120
+
73220
75121
  // src/index.ts
73221
75122
  async function main() {
73222
75123
  const { flags, positionals } = parseArgs(process.argv.slice(2));
@@ -73343,6 +75244,9 @@ async function main() {
73343
75244
  case "ollama":
73344
75245
  await handleOllama(subcommand, rest, flags, context2);
73345
75246
  return;
75247
+ case "fs":
75248
+ await handleFs(subcommand, rest, flags, context2);
75249
+ return;
73346
75250
  default:
73347
75251
  showMainHelp();
73348
75252
  }