@eve-horizon/cli 0.2.20 → 0.2.21

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 +2290 -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 service URLs",
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,175 @@ 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 = await requestJson(ctx, `/projects/${ctx.projectId}`);
51819
+ const envResponse = await requestJson(ctx, `/projects/${ctx.projectId}/envs?limit=50`);
51820
+ const domain = inferDomain(ctx.apiUrl);
51821
+ const environments = [];
51822
+ for (const env of envResponse.data) {
51823
+ if (envFilter && env.name !== envFilter) continue;
51824
+ const status = env.suspended_at ? "suspended" : "active";
51825
+ if (env.suspended_at) {
51826
+ environments.push({
51827
+ name: env.name,
51828
+ type: env.type,
51829
+ status,
51830
+ namespace: env.namespace,
51831
+ services: []
51832
+ });
51833
+ continue;
51834
+ }
51835
+ let services = [];
51836
+ try {
51837
+ const diagnose = await requestJson(ctx, `/projects/${ctx.projectId}/envs/${env.name}/diagnose`);
51838
+ services = buildStatusServices(diagnose.pods, diagnose.namespace ?? env.namespace, domain);
51839
+ } catch {
51840
+ }
51841
+ environments.push({
51842
+ name: env.name,
51843
+ type: env.type,
51844
+ status,
51845
+ namespace: env.namespace,
51846
+ services
51847
+ });
51848
+ }
51849
+ return {
51850
+ api_url: ctx.apiUrl,
51851
+ project_id: project.id,
51852
+ project_name: project.name,
51853
+ environments
51854
+ };
51855
+ }
51856
+ function buildStatusServices(pods, namespace, domain) {
51857
+ const services = /* @__PURE__ */ new Map();
51858
+ for (const pod of pods) {
51859
+ const component = pod.labels["eve.component"] || pod.labels["app.kubernetes.io/name"] || pod.labels["app"] || pod.labels["component"] || "unknown";
51860
+ const existing = services.get(component) ?? { ready: 0, total: 0, phases: /* @__PURE__ */ new Set() };
51861
+ existing.total += 1;
51862
+ if (pod.ready) existing.ready += 1;
51863
+ existing.phases.add(pod.phase);
51864
+ services.set(component, existing);
51865
+ }
51866
+ return Array.from(services.entries()).sort(([a], [b]) => a.localeCompare(b)).map(([name, info]) => {
51867
+ const allDone = info.phases.size > 0 && [...info.phases].every((p) => p === "Succeeded" || p === "Failed");
51868
+ const status = allDone ? "completed" : info.ready === info.total ? "ready" : "not-ready";
51869
+ const url = !allDone && namespace && domain ? buildServiceUrl(name, namespace, domain) : null;
51870
+ return {
51871
+ name,
51872
+ pods_ready: info.ready,
51873
+ pods_total: info.total,
51874
+ status,
51875
+ url
51876
+ };
51877
+ });
51878
+ }
51879
+ function inferDomain(apiUrl) {
51880
+ try {
51881
+ const host = new URL(apiUrl).hostname;
51882
+ if (host.startsWith("api.eve.")) return host.slice("api.eve.".length);
51883
+ if (host.startsWith("api.")) return host.slice("api.".length);
51884
+ return null;
51885
+ } catch {
51886
+ return null;
51887
+ }
51888
+ }
51889
+ function buildServiceUrl(component, namespace, domain) {
51890
+ const slug = namespace.startsWith("eve-") ? namespace.slice(4) : namespace;
51891
+ const secure = !domain.includes("lvh.me") && !domain.includes("localhost");
51892
+ return `${secure ? "https" : "http"}://${component}.${slug}.${domain}`;
51893
+ }
51894
+ function formatStatusOutput(results) {
51895
+ for (let i = 0; i < results.length; i++) {
51896
+ const r = results[i];
51897
+ if (i > 0) console.log("");
51898
+ const marker = r.active ? " (active)" : "";
51899
+ console.log(`${r.name}${marker} ${r.api_url}`);
51900
+ if (r.error) {
51901
+ console.log(` error: ${r.error}`);
51902
+ continue;
51903
+ }
51904
+ const label = r.project_name ? `${r.project_id} (${r.project_name})` : r.project_id;
51905
+ console.log(` project: ${label}`);
51906
+ if (!r.environments || r.environments.length === 0) {
51907
+ console.log(" (no environments)");
51908
+ continue;
51909
+ }
51910
+ for (const env of r.environments) {
51911
+ console.log("");
51912
+ console.log(` ${env.name} ${env.status} ${env.type}`);
51913
+ if (env.services.length === 0) {
51914
+ if (env.status === "suspended") {
51915
+ console.log(" (suspended)");
51916
+ } else {
51917
+ console.log(" (no services)");
51918
+ }
51919
+ continue;
51920
+ }
51921
+ const nameW = Math.max(...env.services.map((s) => s.name.length));
51922
+ const podsW = Math.max(...env.services.map((s) => `${s.pods_ready}/${s.pods_total}`.length));
51923
+ for (const svc of env.services) {
51924
+ const pods = `${svc.pods_ready}/${svc.pods_total}`;
51925
+ const urlPart = svc.url ? ` ${svc.url}` : "";
51926
+ console.log(
51927
+ ` ${padRight(svc.name, nameW)} ${padRight(pods, podsW)} ${padRight(svc.status, 9)}${urlPart}`
51928
+ );
51929
+ }
51930
+ }
51671
51931
  }
51672
51932
  }
51673
51933
  function parseSinceValue2(since) {
@@ -51697,6 +51957,9 @@ function parseSinceValue2(since) {
51697
51957
  }
51698
51958
  return now.toISOString();
51699
51959
  }
51960
+ function padRight(str, width) {
51961
+ return str.length >= width ? str : str + " ".repeat(width - str.length);
51962
+ }
51700
51963
  function buildQuery2(params) {
51701
51964
  const search = new URLSearchParams();
51702
51965
  Object.entries(params).forEach(([key, value]) => {
@@ -55834,6 +56097,8 @@ var configSchema = external_exports.object({
55834
56097
  EVE_GITHUB_TOKEN: external_exports.string().optional(),
55835
56098
  EVE_SLACK_SIGNING_SECRET: external_exports.string().optional(),
55836
56099
  EVE_INTERNAL_API_KEY: external_exports.string().optional(),
56100
+ EVE_ORG_FS_LINK_TOKEN_SECRET: external_exports.string().optional(),
56101
+ EVE_ORG_FS_LINK_TOKEN_TTL_SECONDS: external_exports.coerce.number().int().min(60).default(900),
55837
56102
  EVE_SECRETS_MASTER_KEY: external_exports.string().optional(),
55838
56103
  EVE_DEFAULT_DOMAIN: external_exports.string().optional(),
55839
56104
  // Cluster-level default domain for Ingress (e.g., lvh.me, apps.example.com)
@@ -56961,56 +57226,179 @@ var AccessRoleResponseSchema = external_exports.object({
56961
57226
  updated_at: external_exports.string()
56962
57227
  });
56963
57228
  var AccessRoleListResponseSchema = createApiListResponseSchema(AccessRoleResponseSchema);
57229
+ var AccessPrincipalTypeSchema = external_exports.enum(["user", "service_principal", "group"]);
57230
+ var AccessGroupMemberPrincipalTypeSchema = external_exports.enum(["user", "service_principal"]);
57231
+ var AccessScopePrefixesSchema = external_exports.object({
57232
+ allow_prefixes: external_exports.array(external_exports.string()).optional(),
57233
+ read_only_prefixes: external_exports.array(external_exports.string()).optional()
57234
+ }).strict();
57235
+ var AccessScopeEnvDbSchema = external_exports.object({
57236
+ schemas: external_exports.array(external_exports.string()).optional(),
57237
+ tables: external_exports.array(external_exports.string()).optional()
57238
+ }).strict();
57239
+ var AccessBindingScopeSchema = external_exports.object({
57240
+ orgfs: AccessScopePrefixesSchema.optional(),
57241
+ orgdocs: AccessScopePrefixesSchema.optional(),
57242
+ envdb: AccessScopeEnvDbSchema.optional()
57243
+ }).strict();
56964
57244
  var CreateAccessBindingRequestSchema = external_exports.object({
56965
57245
  role_name: external_exports.string().min(1),
56966
- principal_type: external_exports.enum(["user", "service_principal"]),
57246
+ principal_type: AccessPrincipalTypeSchema,
56967
57247
  principal_id: external_exports.string().min(1),
56968
- project_id: external_exports.string().optional()
57248
+ project_id: external_exports.string().optional(),
57249
+ scope_json: AccessBindingScopeSchema.optional()
56969
57250
  });
56970
57251
  var AccessBindingResponseSchema = external_exports.object({
56971
57252
  id: external_exports.string(),
56972
57253
  role_id: external_exports.string(),
56973
57254
  role_name: external_exports.string(),
56974
- principal_type: external_exports.string(),
57255
+ principal_type: AccessPrincipalTypeSchema,
56975
57256
  principal_id: external_exports.string(),
56976
57257
  project_id: external_exports.string().nullable(),
57258
+ scope_json: AccessBindingScopeSchema.nullable().optional(),
56977
57259
  created_by: external_exports.string().nullable(),
56978
57260
  created_at: external_exports.string()
56979
57261
  });
56980
57262
  var AccessBindingListResponseSchema = createApiListResponseSchema(AccessBindingResponseSchema);
57263
+ var CreateAccessGroupRequestSchema = external_exports.object({
57264
+ name: external_exports.string().min(1).max(100),
57265
+ slug: external_exports.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9-_]*$/).optional(),
57266
+ description: external_exports.string().max(500).optional()
57267
+ });
57268
+ var UpdateAccessGroupRequestSchema = external_exports.object({
57269
+ name: external_exports.string().min(1).max(100).optional(),
57270
+ slug: external_exports.string().min(1).max(100).regex(/^[a-z0-9][a-z0-9-_]*$/).optional(),
57271
+ description: external_exports.string().max(500).nullable().optional()
57272
+ }).refine((value) => Object.keys(value).length > 0, {
57273
+ message: "At least one field is required"
57274
+ });
57275
+ var AccessGroupResponseSchema = external_exports.object({
57276
+ id: external_exports.string(),
57277
+ org_id: external_exports.string(),
57278
+ name: external_exports.string(),
57279
+ slug: external_exports.string(),
57280
+ description: external_exports.string().nullable(),
57281
+ created_by: external_exports.string().nullable(),
57282
+ created_at: external_exports.string(),
57283
+ updated_at: external_exports.string()
57284
+ });
57285
+ var AccessGroupListResponseSchema = createApiListResponseSchema(AccessGroupResponseSchema);
57286
+ var CreateAccessGroupMemberRequestSchema = external_exports.object({
57287
+ principal_type: AccessGroupMemberPrincipalTypeSchema,
57288
+ principal_id: external_exports.string().min(1)
57289
+ });
57290
+ var AccessGroupMemberResponseSchema = external_exports.object({
57291
+ group_id: external_exports.string(),
57292
+ principal_type: AccessGroupMemberPrincipalTypeSchema,
57293
+ principal_id: external_exports.string(),
57294
+ added_by: external_exports.string().nullable(),
57295
+ created_at: external_exports.string()
57296
+ });
57297
+ var AccessGroupMemberListResponseSchema = createApiListResponseSchema(AccessGroupMemberResponseSchema);
56981
57298
  var AccessCanResponseSchema = external_exports.object({
56982
57299
  allowed: external_exports.boolean(),
56983
- source: external_exports.string()
57300
+ source: external_exports.string(),
57301
+ resource: external_exports.object({
57302
+ type: external_exports.enum(["orgfs", "orgdocs", "envdb"]),
57303
+ id: external_exports.string(),
57304
+ action: external_exports.enum(["read", "write", "admin"]),
57305
+ scope_required: external_exports.boolean(),
57306
+ scope_matched: external_exports.boolean()
57307
+ }).optional()
56984
57308
  });
56985
57309
  var AccessExplainGrantSchema = external_exports.object({
56986
57310
  source: external_exports.string(),
56987
57311
  role: external_exports.string().optional(),
56988
57312
  permissions: external_exports.array(external_exports.string()),
56989
- has_permission: external_exports.boolean()
57313
+ has_permission: external_exports.boolean(),
57314
+ scope_json: AccessBindingScopeSchema.nullable().optional(),
57315
+ scope_match: external_exports.boolean().optional(),
57316
+ scope_reason: external_exports.string().optional()
56990
57317
  });
56991
57318
  var AccessExplainResponseSchema = external_exports.object({
56992
57319
  permission: external_exports.string(),
56993
57320
  result: external_exports.enum(["ALLOWED", "DENIED"]),
56994
57321
  grants: external_exports.array(AccessExplainGrantSchema),
56995
- missing_reason: external_exports.string().optional()
57322
+ missing_reason: external_exports.string().optional(),
57323
+ resource: external_exports.object({
57324
+ type: external_exports.enum(["orgfs", "orgdocs", "envdb"]),
57325
+ id: external_exports.string(),
57326
+ action: external_exports.enum(["read", "write", "admin"]),
57327
+ scope_required: external_exports.boolean(),
57328
+ scope_matched: external_exports.boolean()
57329
+ }).optional()
57330
+ });
57331
+ var AccessMembershipBaseSchema = external_exports.object({
57332
+ org_role: external_exports.enum(["owner", "admin", "member"]).nullable(),
57333
+ project_roles: external_exports.array(external_exports.object({
57334
+ project_id: external_exports.string(),
57335
+ role: external_exports.enum(["owner", "admin", "member"])
57336
+ })),
57337
+ token_scopes: external_exports.array(external_exports.string())
57338
+ });
57339
+ var AccessGroupSummarySchema = external_exports.object({
57340
+ id: external_exports.string(),
57341
+ slug: external_exports.string(),
57342
+ name: external_exports.string()
57343
+ });
57344
+ var AccessResolvedBindingSchema = AccessBindingResponseSchema.extend({
57345
+ role_permissions: external_exports.array(external_exports.string()),
57346
+ matched_via: external_exports.enum(["direct", "group"]).optional(),
57347
+ matched_group_id: external_exports.string().nullable().optional(),
57348
+ matched_group_slug: external_exports.string().nullable().optional()
57349
+ });
57350
+ var AccessEffectiveScopeSummarySchema = external_exports.object({
57351
+ orgfs: external_exports.object({
57352
+ allow_prefixes: external_exports.array(external_exports.string()),
57353
+ read_only_prefixes: external_exports.array(external_exports.string())
57354
+ }),
57355
+ orgdocs: external_exports.object({
57356
+ allow_prefixes: external_exports.array(external_exports.string()),
57357
+ read_only_prefixes: external_exports.array(external_exports.string())
57358
+ }),
57359
+ envdb: external_exports.object({
57360
+ schemas: external_exports.array(external_exports.string()),
57361
+ tables: external_exports.array(external_exports.string())
57362
+ })
57363
+ });
57364
+ var AccessPrincipalMembershipsResponseSchema = external_exports.object({
57365
+ org_id: external_exports.string(),
57366
+ principal_type: AccessPrincipalTypeSchema,
57367
+ principal_id: external_exports.string(),
57368
+ base: AccessMembershipBaseSchema,
57369
+ groups: external_exports.array(AccessGroupSummarySchema),
57370
+ direct_bindings: external_exports.array(AccessBindingResponseSchema),
57371
+ effective_bindings: external_exports.array(AccessResolvedBindingSchema),
57372
+ effective_permissions: external_exports.array(external_exports.string()),
57373
+ effective_scopes: AccessEffectiveScopeSummarySchema
56996
57374
  });
56997
57375
  var AccessYamlRoleSchema = external_exports.object({
56998
57376
  scope: external_exports.enum(["org", "project"]),
56999
57377
  description: external_exports.string().optional(),
57000
57378
  permissions: external_exports.array(external_exports.string()).min(1)
57001
57379
  });
57380
+ var AccessYamlGroupMemberSchema = external_exports.object({
57381
+ type: AccessGroupMemberPrincipalTypeSchema,
57382
+ id: external_exports.string().min(1)
57383
+ });
57384
+ var AccessYamlGroupSchema = external_exports.object({
57385
+ name: external_exports.string().min(1).max(100).optional(),
57386
+ description: external_exports.string().max(500).optional(),
57387
+ members: external_exports.array(AccessYamlGroupMemberSchema).optional()
57388
+ });
57002
57389
  var AccessYamlBindingSchema = external_exports.object({
57003
- scope: external_exports.enum(["org", "project"]),
57004
57390
  project_id: external_exports.string().optional(),
57005
57391
  subject: external_exports.object({
57006
- type: external_exports.enum(["user", "service_principal"]),
57392
+ type: AccessPrincipalTypeSchema,
57007
57393
  id: external_exports.string()
57008
57394
  }),
57009
- roles: external_exports.array(external_exports.string()).min(1)
57395
+ roles: external_exports.array(external_exports.string()).min(1),
57396
+ scope: AccessBindingScopeSchema.optional()
57010
57397
  });
57011
57398
  var AccessYamlSchema = external_exports.object({
57012
- version: external_exports.literal(1),
57399
+ version: external_exports.literal(2),
57013
57400
  access: external_exports.object({
57401
+ groups: external_exports.record(external_exports.string().regex(/^[a-z0-9][a-z0-9-_]*$/), AccessYamlGroupSchema).optional(),
57014
57402
  roles: external_exports.record(external_exports.string(), AccessYamlRoleSchema).optional(),
57015
57403
  bindings: external_exports.array(AccessYamlBindingSchema).optional()
57016
57404
  })
@@ -57561,8 +57949,21 @@ var DbRlsTableSchema = external_exports.object({
57561
57949
  rls_enabled: external_exports.boolean(),
57562
57950
  policies: external_exports.array(DbPolicySchema)
57563
57951
  });
57952
+ var DbRlsDiagnosticsContextSchema = external_exports.object({
57953
+ user_id: external_exports.string().nullable(),
57954
+ principal_type: external_exports.enum(["user", "service_principal"]).nullable(),
57955
+ org_id: external_exports.string().nullable(),
57956
+ project_id: external_exports.string().nullable(),
57957
+ env_name: external_exports.string().nullable(),
57958
+ group_ids: external_exports.array(external_exports.string()),
57959
+ permissions: external_exports.array(external_exports.string())
57960
+ });
57961
+ var DbRlsDiagnosticsSchema = external_exports.object({
57962
+ context: DbRlsDiagnosticsContextSchema
57963
+ });
57564
57964
  var DbRlsResponseSchema = external_exports.object({
57565
- tables: external_exports.array(DbRlsTableSchema)
57965
+ tables: external_exports.array(DbRlsTableSchema),
57966
+ diagnostics: DbRlsDiagnosticsSchema
57566
57967
  });
57567
57968
  var DbSqlRequestSchema = external_exports.object({
57568
57969
  sql: external_exports.string().min(1),
@@ -58363,6 +58764,186 @@ var OrgDocumentQueryResponseSchema = external_exports.object({
58363
58764
  })
58364
58765
  });
58365
58766
 
58767
+ // ../shared/dist/schemas/org-fs-sync.js
58768
+ var OrgFsSyncModeSchema = external_exports.enum(["two_way", "push_only", "pull_only"]);
58769
+ var OrgFsLinkStatusSchema = external_exports.enum(["active", "paused", "revoked"]);
58770
+ var OrgFsDeviceStatusSchema = external_exports.enum(["active", "revoked"]);
58771
+ var OrgFsEventSourceSideSchema = external_exports.enum(["local", "remote", "system"]);
58772
+ var OrgFsOwnerPrincipalTypeSchema = external_exports.enum(["user", "service_principal", "system"]);
58773
+ var OrgFsLinkScopeSchema = external_exports.object({
58774
+ allow_prefixes: external_exports.array(external_exports.string().min(1)),
58775
+ read_only_prefixes: external_exports.array(external_exports.string().min(1)).optional()
58776
+ }).passthrough();
58777
+ var OrgFsDeviceSchema = external_exports.object({
58778
+ id: external_exports.string(),
58779
+ org_id: external_exports.string(),
58780
+ device_name: external_exports.string(),
58781
+ platform: external_exports.string().nullable(),
58782
+ client_version: external_exports.string().nullable(),
58783
+ public_key: external_exports.string(),
58784
+ status: OrgFsDeviceStatusSchema,
58785
+ last_seen_at: external_exports.string().nullable(),
58786
+ created_at: external_exports.string(),
58787
+ updated_at: external_exports.string()
58788
+ });
58789
+ var OrgFsEnrollmentSchema = external_exports.object({
58790
+ token: external_exports.string(),
58791
+ expires_at: external_exports.string(),
58792
+ gateway_url: external_exports.string().url()
58793
+ });
58794
+ var OrgFsEnrollDeviceRequestSchema = external_exports.object({
58795
+ device_name: external_exports.string().min(1),
58796
+ platform: external_exports.string().optional(),
58797
+ client_version: external_exports.string().optional(),
58798
+ public_key: external_exports.string().optional()
58799
+ });
58800
+ var OrgFsEnrollDeviceResponseSchema = external_exports.object({
58801
+ device: OrgFsDeviceSchema,
58802
+ enrollment: OrgFsEnrollmentSchema
58803
+ });
58804
+ var OrgFsLinkSchema = external_exports.object({
58805
+ id: external_exports.string(),
58806
+ org_id: external_exports.string(),
58807
+ device_id: external_exports.string(),
58808
+ owner_principal_type: OrgFsOwnerPrincipalTypeSchema,
58809
+ owner_principal_id: external_exports.string().nullable(),
58810
+ mode: OrgFsSyncModeSchema,
58811
+ status: OrgFsLinkStatusSchema,
58812
+ local_path: external_exports.string(),
58813
+ remote_path: external_exports.string(),
58814
+ scope_json: OrgFsLinkScopeSchema,
58815
+ includes: external_exports.array(external_exports.string()),
58816
+ excludes: external_exports.array(external_exports.string()),
58817
+ last_cursor: external_exports.number().int().nonnegative(),
58818
+ lag_ms: external_exports.number().int().nonnegative().nullable().optional(),
58819
+ backlog: external_exports.number().int().nonnegative().optional(),
58820
+ last_synced_at: external_exports.string().nullable(),
58821
+ last_heartbeat_at: external_exports.string().nullable(),
58822
+ updated_at: external_exports.string(),
58823
+ created_at: external_exports.string()
58824
+ });
58825
+ var OrgFsCreateLinkRequestSchema = external_exports.object({
58826
+ device_id: external_exports.string().min(1),
58827
+ mode: OrgFsSyncModeSchema.default("two_way"),
58828
+ local_path: external_exports.string().min(1),
58829
+ remote_path: external_exports.string().min(1).default("/"),
58830
+ allow_prefixes: external_exports.array(external_exports.string().min(1)).min(1).optional(),
58831
+ includes: external_exports.array(external_exports.string()).optional(),
58832
+ excludes: external_exports.array(external_exports.string()).optional()
58833
+ });
58834
+ var OrgFsLinkGatewayTokenSchema = external_exports.object({
58835
+ token: external_exports.string(),
58836
+ expires_at: external_exports.string(),
58837
+ header: external_exports.string().default("x-eve-internal-token"),
58838
+ link_id: external_exports.string(),
58839
+ mode: OrgFsSyncModeSchema,
58840
+ allow_prefixes: external_exports.array(external_exports.string())
58841
+ });
58842
+ var OrgFsCreateLinkResponseSchema = external_exports.object({
58843
+ link: OrgFsLinkSchema,
58844
+ runtime: external_exports.object({
58845
+ sync_engine: external_exports.literal("syncthing"),
58846
+ profile: external_exports.string(),
58847
+ gateway: OrgFsLinkGatewayTokenSchema
58848
+ })
58849
+ });
58850
+ var OrgFsRotateLinkTokenResponseSchema = external_exports.object({
58851
+ gateway: OrgFsLinkGatewayTokenSchema
58852
+ });
58853
+ var OrgFsListLinksResponseSchema = external_exports.object({
58854
+ data: external_exports.array(OrgFsLinkSchema)
58855
+ });
58856
+ var OrgFsUpdateLinkRequestSchema = external_exports.object({
58857
+ mode: OrgFsSyncModeSchema.optional(),
58858
+ status: OrgFsLinkStatusSchema.optional(),
58859
+ allow_prefixes: external_exports.array(external_exports.string().min(1)).min(1).optional(),
58860
+ includes: external_exports.array(external_exports.string()).optional(),
58861
+ excludes: external_exports.array(external_exports.string()).optional()
58862
+ }).refine((value) => Object.keys(value).length > 0, {
58863
+ message: "At least one field is required"
58864
+ });
58865
+ var OrgFsDeleteLinkResponseSchema = external_exports.object({
58866
+ success: external_exports.boolean()
58867
+ });
58868
+ var OrgFsStatusResponseSchema = external_exports.object({
58869
+ org_id: external_exports.string(),
58870
+ gateway: external_exports.object({
58871
+ status: external_exports.enum(["healthy", "degraded", "offline"]),
58872
+ last_heartbeat_at: external_exports.string().nullable()
58873
+ }),
58874
+ links: external_exports.object({
58875
+ active: external_exports.number().int().nonnegative(),
58876
+ paused: external_exports.number().int().nonnegative(),
58877
+ revoked: external_exports.number().int().nonnegative()
58878
+ }),
58879
+ events: external_exports.object({
58880
+ latest_seq: external_exports.number().int().nonnegative()
58881
+ })
58882
+ });
58883
+ var OrgFsEventSchema = external_exports.object({
58884
+ seq: external_exports.number().int().nonnegative(),
58885
+ event_id: external_exports.string(),
58886
+ org_id: external_exports.string(),
58887
+ link_id: external_exports.string().nullable().optional(),
58888
+ device_id: external_exports.string().nullable().optional(),
58889
+ event_type: external_exports.string(),
58890
+ path: external_exports.string(),
58891
+ content_hash: external_exports.string().nullable().optional(),
58892
+ size_bytes: external_exports.number().int().nonnegative().nullable().optional(),
58893
+ source_side: OrgFsEventSourceSideSchema,
58894
+ metadata: external_exports.record(external_exports.unknown()).optional(),
58895
+ created_at: external_exports.string()
58896
+ });
58897
+ var OrgFsEventListResponseSchema = external_exports.object({
58898
+ data: external_exports.array(OrgFsEventSchema),
58899
+ pagination: external_exports.object({
58900
+ limit: external_exports.number().int().positive(),
58901
+ next_after_seq: external_exports.number().int().nonnegative().nullable()
58902
+ })
58903
+ });
58904
+ var OrgFsConflictSchema = external_exports.object({
58905
+ id: external_exports.string(),
58906
+ org_id: external_exports.string(),
58907
+ link_id: external_exports.string().nullable(),
58908
+ path: external_exports.string(),
58909
+ local_hash: external_exports.string().nullable(),
58910
+ remote_hash: external_exports.string().nullable(),
58911
+ status: external_exports.enum(["open", "resolved"]),
58912
+ resolution: external_exports.enum(["pick_local", "pick_remote", "manual"]).nullable(),
58913
+ resolved_by: external_exports.string().nullable(),
58914
+ resolved_at: external_exports.string().nullable(),
58915
+ created_at: external_exports.string()
58916
+ });
58917
+ var OrgFsListConflictsResponseSchema = external_exports.object({
58918
+ data: external_exports.array(OrgFsConflictSchema)
58919
+ });
58920
+ var OrgFsResolveConflictRequestSchema = external_exports.object({
58921
+ strategy: external_exports.enum(["pick_local", "pick_remote", "manual"]),
58922
+ merged_content: external_exports.string().optional()
58923
+ });
58924
+ var OrgFsResolveConflictResponseSchema = external_exports.object({
58925
+ conflict: OrgFsConflictSchema
58926
+ });
58927
+ var OrgFsInternalIngestEventRequestSchema = external_exports.object({
58928
+ event_id: external_exports.string(),
58929
+ link_id: external_exports.string().optional(),
58930
+ device_id: external_exports.string().optional(),
58931
+ event_type: external_exports.string(),
58932
+ path: external_exports.string(),
58933
+ content_hash: external_exports.string().optional(),
58934
+ size_bytes: external_exports.number().int().nonnegative().optional(),
58935
+ source_side: OrgFsEventSourceSideSchema,
58936
+ metadata: external_exports.record(external_exports.unknown()).optional()
58937
+ });
58938
+ var OrgFsInternalHeartbeatRequestSchema = external_exports.object({
58939
+ cursor: external_exports.number().int().nonnegative().optional(),
58940
+ backlog: external_exports.number().int().nonnegative().optional(),
58941
+ lag_ms: external_exports.number().int().nonnegative().optional()
58942
+ });
58943
+ var OrgFsInternalMetricsRequestSchema = external_exports.object({
58944
+ metrics: external_exports.record(external_exports.unknown())
58945
+ });
58946
+
58366
58947
  // ../shared/dist/schemas/resource.js
58367
58948
  var ResolveResourcesRequestSchema = external_exports.object({
58368
58949
  uris: external_exports.array(external_exports.string().min(1)),
@@ -60767,6 +61348,14 @@ var ALL_PERMISSIONS = [
60767
61348
  // Env DB
60768
61349
  "envdb:read",
60769
61350
  "envdb:write",
61351
+ // Org filesystem data plane
61352
+ "orgfs:read",
61353
+ "orgfs:write",
61354
+ "orgfs:admin",
61355
+ // Org document data plane
61356
+ "orgdocs:read",
61357
+ "orgdocs:write",
61358
+ "orgdocs:admin",
60770
61359
  // Secrets
60771
61360
  "secrets:read",
60772
61361
  "secrets:write",
@@ -61688,11 +62277,11 @@ function formatJobsTable(jobs) {
61688
62277
  const phaseWidth = Math.max(5, ...jobs.map((j) => j.phase.length));
61689
62278
  const titleWidth = Math.min(50, Math.max(5, ...jobs.map((j) => j.title.length)));
61690
62279
  const header = [
61691
- padRight("ID", idWidth),
61692
- padRight("P", 2),
61693
- padRight("Phase", phaseWidth),
61694
- padRight("Type", 8),
61695
- padRight("Title", titleWidth),
62280
+ padRight2("ID", idWidth),
62281
+ padRight2("P", 2),
62282
+ padRight2("Phase", phaseWidth),
62283
+ padRight2("Type", 8),
62284
+ padRight2("Title", titleWidth),
61696
62285
  "Assignee"
61697
62286
  ].join(" ");
61698
62287
  console.log(header);
@@ -61700,11 +62289,11 @@ function formatJobsTable(jobs) {
61700
62289
  for (const job of jobs) {
61701
62290
  const title = job.title.length > titleWidth ? job.title.slice(0, titleWidth - 3) + "..." : job.title;
61702
62291
  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),
62292
+ padRight2(job.id, idWidth),
62293
+ padRight2(`P${job.priority}`, 2),
62294
+ padRight2(job.phase, phaseWidth),
62295
+ padRight2(job.issue_type, 8),
62296
+ padRight2(title, titleWidth),
61708
62297
  job.assignee ?? "-"
61709
62298
  ].join(" ");
61710
62299
  console.log(row);
@@ -63344,7 +63933,7 @@ function formatDuration(startStr, endStr) {
63344
63933
  return "unknown";
63345
63934
  }
63346
63935
  }
63347
- function padRight(str, width) {
63936
+ function padRight2(str, width) {
63348
63937
  if (str.length >= width) return str;
63349
63938
  return str + " ".repeat(width - str.length);
63350
63939
  }
@@ -63938,11 +64527,11 @@ Permissions (${data.permissions.length}):`);
63938
64527
  }
63939
64528
  if (status) {
63940
64529
  if (status.completed) {
63941
- throw new Error("Bootstrap already completed. Use eve auth login instead.");
64530
+ console.log("Bootstrap already completed. Attempting server-side recovery flow.");
63942
64531
  }
63943
- if (!status.requires_token && status.window_open) {
64532
+ if (!status.completed && !status.requires_token && status.window_open) {
63944
64533
  console.log(`Bootstrap window open (${status.mode} mode). Token not required.`);
63945
- } else if (status.requires_token && !token) {
64534
+ } else if (!status.completed && status.requires_token && !token) {
63946
64535
  throw new Error("Bootstrap token required. Use --token <token> or set EVE_BOOTSTRAP_TOKEN");
63947
64536
  }
63948
64537
  } else if (!token) {
@@ -64977,10 +65566,10 @@ function renderDiscoveredModels(response) {
64977
65566
  ${response.models.length} model(s) discovered`);
64978
65567
  }
64979
65568
  function formatRow2(columns, widths) {
64980
- const cells = columns.map((value, idx) => ` ${padRight2(value, widths[idx])} `);
65569
+ const cells = columns.map((value, idx) => ` ${padRight3(value, widths[idx])} `);
64981
65570
  return `|${cells.join("|")}|`;
64982
65571
  }
64983
- function padRight2(value, width) {
65572
+ function padRight3(value, width) {
64984
65573
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
64985
65574
  }
64986
65575
 
@@ -65195,7 +65784,7 @@ async function handleSystem(subcommand, positionals, flags, context2) {
65195
65784
  const json = Boolean(flags.json);
65196
65785
  switch (subcommand) {
65197
65786
  case "status":
65198
- return handleStatus(context2, json);
65787
+ return handleStatus2(context2, json);
65199
65788
  case "health":
65200
65789
  return handleHealth(context2, json);
65201
65790
  case "jobs":
@@ -65218,7 +65807,7 @@ async function handleSystem(subcommand, positionals, flags, context2) {
65218
65807
  throw new Error("Usage: eve system <status|health|jobs|envs|logs|pods|events|config|settings|orchestrator>");
65219
65808
  }
65220
65809
  }
65221
- async function handleStatus(context2, json) {
65810
+ async function handleStatus2(context2, json) {
65222
65811
  try {
65223
65812
  const status = await requestJson(context2, "/system/status");
65224
65813
  if (json) {
@@ -65584,11 +66173,11 @@ function formatJobsTable2(jobs) {
65584
66173
  const phaseWidth = Math.max(5, ...jobs.map((j) => j.phase.length));
65585
66174
  const titleWidth = Math.min(40, Math.max(5, ...jobs.map((j) => j.title.length)));
65586
66175
  const header = [
65587
- padRight3("Job ID", idWidth),
65588
- padRight3("Project", projectWidth),
65589
- padRight3("Phase", phaseWidth),
65590
- padRight3("P", 2),
65591
- padRight3("Title", titleWidth),
66176
+ padRight4("Job ID", idWidth),
66177
+ padRight4("Project", projectWidth),
66178
+ padRight4("Phase", phaseWidth),
66179
+ padRight4("P", 2),
66180
+ padRight4("Title", titleWidth),
65592
66181
  "Assignee"
65593
66182
  ].join(" ");
65594
66183
  console.log(header);
@@ -65596,11 +66185,11 @@ function formatJobsTable2(jobs) {
65596
66185
  for (const job of jobs) {
65597
66186
  const title = job.title.length > titleWidth ? job.title.slice(0, titleWidth - 3) + "..." : job.title;
65598
66187
  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),
66188
+ padRight4(job.id, idWidth),
66189
+ padRight4(job.project_id, projectWidth),
66190
+ padRight4(job.phase, phaseWidth),
66191
+ padRight4(`P${job.priority}`, 2),
66192
+ padRight4(title, titleWidth),
65604
66193
  job.assignee ?? "-"
65605
66194
  ].join(" ");
65606
66195
  console.log(row);
@@ -65619,21 +66208,21 @@ function formatEnvsTable(envs) {
65619
66208
  const namespaceWidth = Math.max(9, ...envs.map((e) => (e.namespace ?? "-").length));
65620
66209
  const releaseWidth = Math.max(7, ...envs.map((e) => (e.current_release ?? "-").length));
65621
66210
  const header = [
65622
- padRight3("Project", projectWidth),
65623
- padRight3("Environment", nameWidth),
65624
- padRight3("Type", typeWidth),
65625
- padRight3("Namespace", namespaceWidth),
65626
- padRight3("Release", releaseWidth)
66211
+ padRight4("Project", projectWidth),
66212
+ padRight4("Environment", nameWidth),
66213
+ padRight4("Type", typeWidth),
66214
+ padRight4("Namespace", namespaceWidth),
66215
+ padRight4("Release", releaseWidth)
65627
66216
  ].join(" ");
65628
66217
  console.log(header);
65629
66218
  console.log("-".repeat(header.length));
65630
66219
  for (const env of envs) {
65631
66220
  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)
66221
+ padRight4(env.project_id, projectWidth),
66222
+ padRight4(env.name, nameWidth),
66223
+ padRight4(env.type, typeWidth),
66224
+ padRight4(env.namespace ?? "-", namespaceWidth),
66225
+ padRight4(env.current_release ?? "-", releaseWidth)
65637
66226
  ].join(" ");
65638
66227
  console.log(row);
65639
66228
  }
@@ -65652,12 +66241,12 @@ function formatPodsTable(pods) {
65652
66241
  const restartsWidth = Math.max(8, ...pods.map((p) => String(p.restarts).length));
65653
66242
  const ageWidth = Math.max(3, ...pods.map((p) => p.age.length));
65654
66243
  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),
66244
+ padRight4("Name", nameWidth),
66245
+ padRight4("Namespace", nsWidth),
66246
+ padRight4("Phase", phaseWidth),
66247
+ padRight4("Ready", readyWidth),
66248
+ padRight4("Restarts", restartsWidth),
66249
+ padRight4("Age", ageWidth),
65661
66250
  "Component",
65662
66251
  "Org",
65663
66252
  "Project",
@@ -65667,20 +66256,20 @@ function formatPodsTable(pods) {
65667
66256
  console.log("-".repeat(header.length));
65668
66257
  for (const pod of pods) {
65669
66258
  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)
66259
+ padRight4(pod.name, nameWidth),
66260
+ padRight4(pod.namespace, nsWidth),
66261
+ padRight4(pod.phase, phaseWidth),
66262
+ padRight4(pod.ready ? "yes" : "no", readyWidth),
66263
+ padRight4(String(pod.restarts), restartsWidth),
66264
+ padRight4(pod.age, ageWidth),
66265
+ padRight4(pod.component ?? "-", 10),
66266
+ padRight4(pod.orgId ?? "-", 10),
66267
+ padRight4(pod.projectId ?? "-", 10),
66268
+ padRight4(pod.env ?? "-", 10)
65680
66269
  ].join(" "));
65681
66270
  }
65682
66271
  }
65683
- function padRight3(str, width) {
66272
+ function padRight4(str, width) {
65684
66273
  if (str.length >= width) return str;
65685
66274
  return str + " ".repeat(width - str.length);
65686
66275
  }
@@ -65897,6 +66486,8 @@ function stripFilePath(filePath) {
65897
66486
  }
65898
66487
 
65899
66488
  // src/commands/env.ts
66489
+ var import_node_fs7 = require("node:fs");
66490
+ var import_node_path7 = require("node:path");
65900
66491
  var readline2 = __toESM(require("node:readline/promises"));
65901
66492
  async function handleEnv(subcommand, positionals, flags, context2) {
65902
66493
  const json = Boolean(flags.json);
@@ -66048,12 +66639,47 @@ async function handleDeploy(positionals, flags, context2, json) {
66048
66639
  if (!json) {
66049
66640
  console.log(`Deploying commit ${gitSha.substring(0, 8)} to ${envName}...`);
66050
66641
  }
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)}...`);
66642
+ let manifestHash;
66643
+ if (repoDir) {
66644
+ const manifestPath = (0, import_node_path7.join)(repoDir, ".eve", "manifest.yaml");
66645
+ if ((0, import_node_fs7.existsSync)(manifestPath)) {
66646
+ const manifestYaml = (0, import_node_fs7.readFileSync)(manifestPath, "utf-8");
66647
+ const branch = getGitBranch(repoDir);
66648
+ const syncResponse = await requestJson(
66649
+ context2,
66650
+ `/projects/${projectId}/manifest`,
66651
+ {
66652
+ method: "POST",
66653
+ body: {
66654
+ yaml: manifestYaml,
66655
+ git_sha: gitSha,
66656
+ branch
66657
+ }
66658
+ }
66659
+ );
66660
+ manifestHash = syncResponse.manifest_hash;
66661
+ if (!json) {
66662
+ console.log(`Synced manifest ${manifestHash.substring(0, 8)}...`);
66663
+ }
66664
+ } else {
66665
+ const manifest = await requestJson(
66666
+ context2,
66667
+ `/projects/${projectId}/manifest`
66668
+ );
66669
+ manifestHash = manifest.manifest_hash;
66670
+ if (!json) {
66671
+ console.log(`Using manifest ${manifestHash.substring(0, 8)}...`);
66672
+ }
66673
+ }
66674
+ } else {
66675
+ const manifest = await requestJson(
66676
+ context2,
66677
+ `/projects/${projectId}/manifest`
66678
+ );
66679
+ manifestHash = manifest.manifest_hash;
66680
+ if (!json) {
66681
+ console.log(`Using manifest ${manifestHash.substring(0, 8)}...`);
66682
+ }
66057
66683
  }
66058
66684
  const direct = Boolean(flags.direct);
66059
66685
  let inputs;
@@ -66074,7 +66700,7 @@ Example: --inputs '{"release_id":"rel_xxx","smoke_test":false}'`
66074
66700
  const imageTag = getStringFlag(flags, ["image-tag", "image_tag", "imageTag"]);
66075
66701
  const body = {
66076
66702
  git_sha: gitSha,
66077
- manifest_hash: manifest.manifest_hash
66703
+ manifest_hash: manifestHash
66078
66704
  };
66079
66705
  if (direct) {
66080
66706
  body.direct = true;
@@ -66351,9 +66977,9 @@ function formatEnvironmentsTable(environments) {
66351
66977
  const typeWidth = Math.max(4, ...environments.map((e) => e.type.length));
66352
66978
  const namespaceWidth = Math.max(9, ...environments.map((e) => e.namespace?.length ?? 0));
66353
66979
  const header = [
66354
- padRight4("Name", nameWidth),
66355
- padRight4("Type", typeWidth),
66356
- padRight4("Namespace", namespaceWidth),
66980
+ padRight5("Name", nameWidth),
66981
+ padRight5("Type", typeWidth),
66982
+ padRight5("Namespace", namespaceWidth),
66357
66983
  "Current Release"
66358
66984
  ].join(" ");
66359
66985
  console.log(header);
@@ -66361,9 +66987,9 @@ function formatEnvironmentsTable(environments) {
66361
66987
  for (const env of environments) {
66362
66988
  const releaseDisplay = env.current_release_id ? env.current_release_id.substring(0, 12) + "..." : "-";
66363
66989
  const row = [
66364
- padRight4(env.name, nameWidth),
66365
- padRight4(env.type, typeWidth),
66366
- padRight4(env.namespace || "-", namespaceWidth),
66990
+ padRight5(env.name, nameWidth),
66991
+ padRight5(env.type, typeWidth),
66992
+ padRight5(env.namespace || "-", namespaceWidth),
66367
66993
  releaseDisplay
66368
66994
  ].join(" ");
66369
66995
  console.log(row);
@@ -66455,21 +67081,21 @@ function formatEnvServices(report, services) {
66455
67081
  const restartsWidth = Math.max(8, ...services.map((s) => String(s.restarts).length));
66456
67082
  const phasesWidth = Math.max(6, ...services.map((s) => s.phases.join(", ").length));
66457
67083
  const header = [
66458
- padRight4("Service", nameWidth),
66459
- padRight4("Pods", totalWidth),
66460
- padRight4("Ready", readyWidth),
66461
- padRight4("Restarts", restartsWidth),
66462
- padRight4("Phases", phasesWidth)
67084
+ padRight5("Service", nameWidth),
67085
+ padRight5("Pods", totalWidth),
67086
+ padRight5("Ready", readyWidth),
67087
+ padRight5("Restarts", restartsWidth),
67088
+ padRight5("Phases", phasesWidth)
66463
67089
  ].join(" ");
66464
67090
  console.log(header);
66465
67091
  console.log("-".repeat(header.length));
66466
67092
  for (const service of services) {
66467
67093
  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)
67094
+ padRight5(service.name, nameWidth),
67095
+ padRight5(String(service.pods_total), totalWidth),
67096
+ padRight5(String(service.pods_ready), readyWidth),
67097
+ padRight5(String(service.restarts), restartsWidth),
67098
+ padRight5(service.phases.join(", "), phasesWidth)
66473
67099
  ].join(" "));
66474
67100
  }
66475
67101
  }
@@ -66479,8 +67105,8 @@ function formatEnvServices(report, services) {
66479
67105
  const nameWidth = Math.max(4, ...report.deployments.map((d) => d.name.length));
66480
67106
  const readyWidth = Math.max(5, ...report.deployments.map((d) => `${d.available_replicas}/${d.desired_replicas}`.length));
66481
67107
  const header = [
66482
- padRight4("Name", nameWidth),
66483
- padRight4("Ready", readyWidth),
67108
+ padRight5("Name", nameWidth),
67109
+ padRight5("Ready", readyWidth),
66484
67110
  "Status"
66485
67111
  ].join(" ");
66486
67112
  console.log(header);
@@ -66489,8 +67115,8 @@ function formatEnvServices(report, services) {
66489
67115
  const readiness = `${deployment.available_replicas}/${deployment.desired_replicas}`;
66490
67116
  const status = deployment.ready ? "ready" : "not-ready";
66491
67117
  console.log([
66492
- padRight4(deployment.name, nameWidth),
66493
- padRight4(readiness, readyWidth),
67118
+ padRight5(deployment.name, nameWidth),
67119
+ padRight5(readiness, readyWidth),
66494
67120
  status
66495
67121
  ].join(" "));
66496
67122
  }
@@ -66519,8 +67145,8 @@ function formatEnvDiagnose(report) {
66519
67145
  const nameWidth = Math.max(4, ...report.deployments.map((d) => d.name.length));
66520
67146
  const readyWidth = Math.max(5, ...report.deployments.map((d) => `${d.available_replicas}/${d.desired_replicas}`.length));
66521
67147
  const header = [
66522
- padRight4("Name", nameWidth),
66523
- padRight4("Ready", readyWidth),
67148
+ padRight5("Name", nameWidth),
67149
+ padRight5("Ready", readyWidth),
66524
67150
  "Status"
66525
67151
  ].join(" ");
66526
67152
  console.log(header);
@@ -66529,8 +67155,8 @@ function formatEnvDiagnose(report) {
66529
67155
  const readiness = `${deployment.available_replicas}/${deployment.desired_replicas}`;
66530
67156
  const status = deployment.ready ? "ready" : "not-ready";
66531
67157
  console.log([
66532
- padRight4(deployment.name, nameWidth),
66533
- padRight4(readiness, readyWidth),
67158
+ padRight5(deployment.name, nameWidth),
67159
+ padRight5(readiness, readyWidth),
66534
67160
  status
66535
67161
  ].join(" "));
66536
67162
  }
@@ -66542,9 +67168,9 @@ function formatEnvDiagnose(report) {
66542
67168
  const phaseWidth = Math.max(5, ...report.pods.map((p) => p.phase.length));
66543
67169
  const restartsWidth = Math.max(8, ...report.pods.map((p) => String(p.restarts).length));
66544
67170
  const header = [
66545
- padRight4("Name", nameWidth),
66546
- padRight4("Phase", phaseWidth),
66547
- padRight4("Restarts", restartsWidth),
67171
+ padRight5("Name", nameWidth),
67172
+ padRight5("Phase", phaseWidth),
67173
+ padRight5("Restarts", restartsWidth),
66548
67174
  "Ready",
66549
67175
  "Age"
66550
67176
  ].join(" ");
@@ -66552,9 +67178,9 @@ function formatEnvDiagnose(report) {
66552
67178
  console.log("-".repeat(header.length));
66553
67179
  for (const pod of report.pods) {
66554
67180
  console.log([
66555
- padRight4(pod.name, nameWidth),
66556
- padRight4(pod.phase, phaseWidth),
66557
- padRight4(String(pod.restarts), restartsWidth),
67181
+ padRight5(pod.name, nameWidth),
67182
+ padRight5(pod.phase, phaseWidth),
67183
+ padRight5(String(pod.restarts), restartsWidth),
66558
67184
  pod.ready ? "yes" : "no",
66559
67185
  pod.age
66560
67186
  ].join(" "));
@@ -66654,7 +67280,7 @@ function formatDate2(dateStr) {
66654
67280
  return dateStr;
66655
67281
  }
66656
67282
  }
66657
- function padRight4(str, width) {
67283
+ function padRight5(str, width) {
66658
67284
  if (str.length >= width) return str;
66659
67285
  return str + " ".repeat(width - str.length);
66660
67286
  }
@@ -67552,8 +68178,8 @@ function formatLifecycleMeta2(phase, meta) {
67552
68178
  }
67553
68179
 
67554
68180
  // src/commands/api.ts
67555
- var import_node_fs7 = require("node:fs");
67556
- var import_node_path7 = require("node:path");
68181
+ var import_node_fs8 = require("node:fs");
68182
+ var import_node_path8 = require("node:path");
67557
68183
  var import_node_child_process6 = require("node:child_process");
67558
68184
  var import_node_os4 = require("node:os");
67559
68185
  async function handleApi(subcommand, positionals, flags, context2) {
@@ -67672,7 +68298,7 @@ async function handleExamples(positionals, flags, context2, jsonOutput) {
67672
68298
  async function handleGenerate(positionals, flags, jsonOutput) {
67673
68299
  const outDir = getStringFlag(flags, ["out"]) ?? positionals[0];
67674
68300
  const repoRoot = resolveRepoRoot();
67675
- const outputDir = outDir ? (0, import_node_path7.resolve)(repoRoot, outDir) : (0, import_node_path7.resolve)(repoRoot, "docs/system");
68301
+ const outputDir = outDir ? (0, import_node_path8.resolve)(repoRoot, outDir) : (0, import_node_path8.resolve)(repoRoot, "docs/system");
67676
68302
  runOpenApiExport(repoRoot, outputDir);
67677
68303
  outputJson({ ok: true, output_dir: outputDir }, jsonOutput, `OpenAPI exported to ${outputDir}`);
67678
68304
  }
@@ -67680,13 +68306,13 @@ async function handleDiff(positionals, flags, jsonOutput) {
67680
68306
  const exitCode = Boolean(flags["exit-code"]);
67681
68307
  const repoRoot = resolveRepoRoot();
67682
68308
  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-"));
68309
+ const targetDir = expectedDir ? (0, import_node_path8.resolve)(repoRoot, expectedDir) : (0, import_node_path8.resolve)(repoRoot, "docs/system");
68310
+ const tempDir = (0, import_node_fs8.mkdtempSync)((0, import_node_path8.join)((0, import_node_os4.tmpdir)(), "eve-openapi-"));
67685
68311
  try {
67686
68312
  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)) {
68313
+ const actualPath = (0, import_node_path8.resolve)(tempDir, "openapi.yaml");
68314
+ const expectedPath = (0, import_node_path8.resolve)(targetDir, "openapi.yaml");
68315
+ if (!(0, import_node_fs8.existsSync)(expectedPath)) {
67690
68316
  throw new Error(`Missing expected OpenAPI spec at ${expectedPath}`);
67691
68317
  }
67692
68318
  const diff = (0, import_node_child_process6.spawnSync)("diff", ["-u", expectedPath, actualPath], { stdio: "inherit" });
@@ -67696,7 +68322,7 @@ async function handleDiff(positionals, flags, jsonOutput) {
67696
68322
  }
67697
68323
  outputJson({ ok: !hasDiff }, jsonOutput, hasDiff ? "OpenAPI spec drift detected" : "OpenAPI spec matches");
67698
68324
  } finally {
67699
- (0, import_node_fs7.rmSync)(tempDir, { recursive: true, force: true });
68325
+ (0, import_node_fs8.rmSync)(tempDir, { recursive: true, force: true });
67700
68326
  }
67701
68327
  }
67702
68328
  async function handleCall(positionals, flags, context2) {
@@ -67985,10 +68611,10 @@ function stripTrailingSlash(url) {
67985
68611
  function resolveRepoRoot() {
67986
68612
  let current = process.cwd();
67987
68613
  while (true) {
67988
- if ((0, import_node_fs7.existsSync)((0, import_node_path7.resolve)(current, "pnpm-workspace.yaml"))) {
68614
+ if ((0, import_node_fs8.existsSync)((0, import_node_path8.resolve)(current, "pnpm-workspace.yaml"))) {
67989
68615
  return current;
67990
68616
  }
67991
- const parent = (0, import_node_path7.dirname)(current);
68617
+ const parent = (0, import_node_path8.dirname)(current);
67992
68618
  if (parent === current) {
67993
68619
  break;
67994
68620
  }
@@ -67997,9 +68623,9 @@ function resolveRepoRoot() {
67997
68623
  throw new Error("Unable to locate repo root (pnpm-workspace.yaml not found).");
67998
68624
  }
67999
68625
  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" });
68626
+ const scriptPath = (0, import_node_path8.resolve)(repoRoot, "apps/api/dist/scripts/export-openapi.js");
68627
+ if (!(0, import_node_fs8.existsSync)(scriptPath)) {
68628
+ const build = (0, import_node_child_process6.spawnSync)("pnpm", ["-C", (0, import_node_path8.resolve)(repoRoot, "apps/api"), "build"], { stdio: "inherit" });
68003
68629
  if (build.status !== 0) {
68004
68630
  throw new Error("Failed to build API before exporting OpenAPI spec");
68005
68631
  }
@@ -68035,14 +68661,14 @@ function resolveJsonInput(value) {
68035
68661
  }
68036
68662
  function resolveTextInput(value) {
68037
68663
  if (value.startsWith("@")) {
68038
- const filePath = (0, import_node_path7.resolve)(value.slice(1));
68039
- return (0, import_node_fs7.readFileSync)(filePath, "utf-8");
68664
+ const filePath = (0, import_node_path8.resolve)(value.slice(1));
68665
+ return (0, import_node_fs8.readFileSync)(filePath, "utf-8");
68040
68666
  }
68041
68667
  if (value.trim().startsWith("{") || value.trim().startsWith("[")) {
68042
68668
  return value;
68043
68669
  }
68044
- if ((0, import_node_fs7.existsSync)(value)) {
68045
- return (0, import_node_fs7.readFileSync)((0, import_node_path7.resolve)(value), "utf-8");
68670
+ if ((0, import_node_fs8.existsSync)(value)) {
68671
+ return (0, import_node_fs8.readFileSync)((0, import_node_path8.resolve)(value), "utf-8");
68046
68672
  }
68047
68673
  return value;
68048
68674
  }
@@ -68085,8 +68711,8 @@ function buildCurlCommand(options) {
68085
68711
  }
68086
68712
 
68087
68713
  // src/commands/db.ts
68088
- var import_node_fs8 = require("node:fs");
68089
- var import_node_path8 = require("node:path");
68714
+ var import_node_fs9 = require("node:fs");
68715
+ var import_node_path9 = require("node:path");
68090
68716
  var MIGRATION_REGEX = /^(\d{14})_([a-z0-9_]+)\.sql$/;
68091
68717
  async function handleDb(subcommand, positionals, flags, context2) {
68092
68718
  const jsonOutput = Boolean(flags.json);
@@ -68104,7 +68730,7 @@ async function handleDb(subcommand, positionals, flags, context2) {
68104
68730
  case "new":
68105
68731
  return handleNew(positionals, flags);
68106
68732
  case "status":
68107
- return handleStatus2(positionals, flags, context2, jsonOutput);
68733
+ return handleStatus3(positionals, flags, context2, jsonOutput);
68108
68734
  case "rotate-credentials":
68109
68735
  return handleRotateCredentials(positionals, flags, context2, jsonOutput);
68110
68736
  case "scale":
@@ -68113,7 +68739,7 @@ async function handleDb(subcommand, positionals, flags, context2) {
68113
68739
  return handleDestroy(positionals, flags, context2, jsonOutput);
68114
68740
  default:
68115
68741
  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"
68742
+ "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
68743
  );
68118
68744
  }
68119
68745
  }
@@ -68123,9 +68749,109 @@ async function handleSchema(positionals, flags, context2, jsonOutput) {
68123
68749
  outputJson(response, jsonOutput);
68124
68750
  }
68125
68751
  async function handleRls(positionals, flags, context2, jsonOutput) {
68752
+ if (positionals[0] === "init") {
68753
+ return handleRlsInit(flags, jsonOutput);
68754
+ }
68126
68755
  const { projectId, envName } = resolveProjectEnv(positionals, flags, context2);
68127
68756
  const response = await requestJson(context2, `/projects/${projectId}/envs/${envName}/db/rls`);
68128
- outputJson(response, jsonOutput);
68757
+ if (jsonOutput) {
68758
+ outputJson(response, true);
68759
+ return;
68760
+ }
68761
+ printRlsResponse(response);
68762
+ }
68763
+ function handleRlsInit(flags, jsonOutput) {
68764
+ const withGroups = toBoolean(flags["with-groups"]) ?? false;
68765
+ const force = toBoolean(flags.force) ?? false;
68766
+ if (!withGroups) {
68767
+ throw new Error("Usage: eve db rls init --with-groups [--out <path>] [--force]");
68768
+ }
68769
+ const outPath = getStringFlag(flags, ["out"]) ?? "db/rls/helpers.sql";
68770
+ const fullPath = (0, import_node_path9.resolve)(outPath);
68771
+ if ((0, import_node_fs9.existsSync)(fullPath) && !force) {
68772
+ throw new Error(`RLS helper file already exists: ${fullPath}
68773
+ Use --force to overwrite.`);
68774
+ }
68775
+ (0, import_node_fs9.mkdirSync)((0, import_node_path9.dirname)(fullPath), { recursive: true });
68776
+ (0, import_node_fs9.writeFileSync)(fullPath, renderRlsHelperTemplate());
68777
+ const payload = {
68778
+ path: fullPath,
68779
+ with_groups: true
68780
+ };
68781
+ outputJson(payload, jsonOutput, `Created group-aware RLS helpers at ${fullPath}`);
68782
+ }
68783
+ function renderRlsHelperTemplate() {
68784
+ return `-- Eve RLS helpers generated by "eve db rls init --with-groups"
68785
+ -- Usage:
68786
+ -- 1. Apply this SQL in your target environment DB.
68787
+ -- 2. Reference app.current_user_id()/app.current_group_ids()/app.has_group() in policies.
68788
+ --
68789
+ -- Example:
68790
+ -- CREATE POLICY notes_group_read
68791
+ -- ON notes
68792
+ -- FOR SELECT
68793
+ -- USING (group_id = ANY(app.current_group_ids()));
68794
+
68795
+ CREATE SCHEMA IF NOT EXISTS app;
68796
+
68797
+ CREATE OR REPLACE FUNCTION app.current_user_id()
68798
+ RETURNS text
68799
+ LANGUAGE sql
68800
+ STABLE
68801
+ AS $$
68802
+ SELECT NULLIF(current_setting('app.user_id', true), '');
68803
+ $$;
68804
+
68805
+ CREATE OR REPLACE FUNCTION app.current_group_ids()
68806
+ RETURNS text[]
68807
+ LANGUAGE sql
68808
+ STABLE
68809
+ AS $$
68810
+ WITH raw AS (
68811
+ SELECT NULLIF(current_setting('app.group_ids', true), '') AS value
68812
+ )
68813
+ SELECT COALESCE(
68814
+ ARRAY(
68815
+ SELECT jsonb_array_elements_text(COALESCE(raw.value::jsonb, '[]'::jsonb))
68816
+ FROM raw
68817
+ ),
68818
+ ARRAY[]::text[]
68819
+ );
68820
+ $$;
68821
+
68822
+ CREATE OR REPLACE FUNCTION app.has_group(group_id text)
68823
+ RETURNS boolean
68824
+ LANGUAGE sql
68825
+ STABLE
68826
+ AS $$
68827
+ SELECT group_id = ANY(app.current_group_ids());
68828
+ $$;
68829
+ `;
68830
+ }
68831
+ function printRlsResponse(response) {
68832
+ const diagnostics = response.diagnostics?.context;
68833
+ if (diagnostics) {
68834
+ const groups = diagnostics.group_ids ?? [];
68835
+ const permissions = diagnostics.permissions ?? [];
68836
+ console.log("Context:");
68837
+ console.log(` Principal: ${diagnostics.principal_type ?? "unknown"} ${diagnostics.user_id ?? "(none)"}`);
68838
+ console.log(` Org: ${diagnostics.org_id ?? "(none)"}`);
68839
+ console.log(` Project: ${diagnostics.project_id ?? "(none)"}`);
68840
+ console.log(` Env: ${diagnostics.env_name ?? "(none)"}`);
68841
+ console.log(` Groups (${groups.length}): ${groups.length > 0 ? groups.join(", ") : "(none)"}`);
68842
+ console.log(` Permissions (${permissions.length}): ${permissions.length > 0 ? permissions.join(", ") : "(none)"}`);
68843
+ console.log("");
68844
+ }
68845
+ const tables = response.tables ?? [];
68846
+ if (tables.length === 0) {
68847
+ console.log("No user tables/views found.");
68848
+ return;
68849
+ }
68850
+ console.log(`RLS tables (${tables.length}):`);
68851
+ for (const table of tables) {
68852
+ const state = table.rls_enabled ? "enabled" : "disabled";
68853
+ console.log(` - ${table.schema}.${table.name} (${state}, ${table.policies.length} policies)`);
68854
+ }
68129
68855
  }
68130
68856
  async function handleSql(positionals, flags, context2, jsonOutput) {
68131
68857
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
@@ -68137,7 +68863,7 @@ async function handleSql(positionals, flags, context2, jsonOutput) {
68137
68863
  const sqlPositionals = envFromFlag ? positionals : positionals.slice(1);
68138
68864
  const sqlInput = getStringFlag(flags, ["sql"]) ?? sqlPositionals.join(" ");
68139
68865
  const fileInput = getStringFlag(flags, ["file"]);
68140
- const sqlText = fileInput ? (0, import_node_fs8.readFileSync)((0, import_node_path8.resolve)(fileInput), "utf-8") : sqlInput;
68866
+ const sqlText = fileInput ? (0, import_node_fs9.readFileSync)((0, import_node_path9.resolve)(fileInput), "utf-8") : sqlInput;
68141
68867
  if (!sqlText) {
68142
68868
  throw new Error("Usage: eve db sql --env <name> --sql <statement> [--params <json>] [--write]");
68143
68869
  }
@@ -68176,11 +68902,11 @@ function parseJson(value, label) {
68176
68902
  async function handleMigrate(positionals, flags, context2, jsonOutput) {
68177
68903
  const { projectId, envName } = resolveProjectEnv(positionals, flags, context2);
68178
68904
  const migrationsPath = getStringFlag(flags, ["path"]) ?? "db/migrations";
68179
- const fullPath = (0, import_node_path8.resolve)(migrationsPath);
68180
- if (!(0, import_node_fs8.existsSync)(fullPath)) {
68905
+ const fullPath = (0, import_node_path9.resolve)(migrationsPath);
68906
+ if (!(0, import_node_fs9.existsSync)(fullPath)) {
68181
68907
  throw new Error(`Migrations directory not found: ${fullPath}`);
68182
68908
  }
68183
- const files = (0, import_node_fs8.readdirSync)(fullPath).filter((f) => f.endsWith(".sql")).sort();
68909
+ const files = (0, import_node_fs9.readdirSync)(fullPath).filter((f) => f.endsWith(".sql")).sort();
68184
68910
  if (files.length === 0) {
68185
68911
  console.log("No migration files found");
68186
68912
  return;
@@ -68196,7 +68922,7 @@ Example: 20260128100000_create_users.sql`
68196
68922
  }
68197
68923
  const migrations = files.map((file) => ({
68198
68924
  name: file,
68199
- sql: (0, import_node_fs8.readFileSync)((0, import_node_path8.join)(fullPath, file), "utf-8")
68925
+ sql: (0, import_node_fs9.readFileSync)((0, import_node_path9.join)(fullPath, file), "utf-8")
68200
68926
  }));
68201
68927
  console.log(`Found ${migrations.length} migration files`);
68202
68928
  const response = await requestJson(context2, `/projects/${projectId}/envs/${envName}/db/migrate`, {
@@ -68258,12 +68984,12 @@ function handleNew(positionals, flags) {
68258
68984
  ].join("");
68259
68985
  const filename = `${timestamp}_${normalizedDescription}.sql`;
68260
68986
  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 });
68987
+ const fullPath = (0, import_node_path9.resolve)(migrationsPath);
68988
+ if (!(0, import_node_fs9.existsSync)(fullPath)) {
68989
+ (0, import_node_fs9.mkdirSync)(fullPath, { recursive: true });
68264
68990
  }
68265
- const filePath = (0, import_node_path8.join)(fullPath, filename);
68266
- if ((0, import_node_fs8.existsSync)(filePath)) {
68991
+ const filePath = (0, import_node_path9.join)(fullPath, filename);
68992
+ if ((0, import_node_fs9.existsSync)(filePath)) {
68267
68993
  throw new Error(`Migration file already exists: ${filePath}`);
68268
68994
  }
68269
68995
  const template = `-- Migration: ${normalizedDescription}
@@ -68272,10 +68998,10 @@ function handleNew(positionals, flags) {
68272
68998
  -- Write your SQL migration here
68273
68999
 
68274
69000
  `;
68275
- (0, import_node_fs8.writeFileSync)(filePath, template);
69001
+ (0, import_node_fs9.writeFileSync)(filePath, template);
68276
69002
  console.log(`Created: ${filePath}`);
68277
69003
  }
68278
- async function handleStatus2(positionals, flags, context2, jsonOutput) {
69004
+ async function handleStatus3(positionals, flags, context2, jsonOutput) {
68279
69005
  const { projectId, envName } = resolveProjectEnv(positionals, flags, context2);
68280
69006
  const response = await requestJson(context2, `/projects/${projectId}/envs/${envName}/db/managed`);
68281
69007
  if (jsonOutput) {
@@ -68509,24 +69235,24 @@ function formatEventsTable(events) {
68509
69235
  const sourceWidth = Math.max(6, ...events.map((e) => e.source.length));
68510
69236
  const statusWidth = Math.max(6, ...events.map((e) => e.status.length));
68511
69237
  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),
69238
+ padRight6("ID", idWidth),
69239
+ padRight6("Type", typeWidth),
69240
+ padRight6("Source", sourceWidth),
69241
+ padRight6("Status", statusWidth),
69242
+ padRight6("Env", 12),
69243
+ padRight6("Branch", 20),
68518
69244
  "Created"
68519
69245
  ].join(" ");
68520
69246
  console.log(header);
68521
69247
  console.log("-".repeat(header.length));
68522
69248
  for (const event of events) {
68523
69249
  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),
69250
+ padRight6(event.id, idWidth),
69251
+ padRight6(event.type, typeWidth),
69252
+ padRight6(event.source, sourceWidth),
69253
+ padRight6(event.status, statusWidth),
69254
+ padRight6(event.env_name || "-", 12),
69255
+ padRight6(event.ref_branch || "-", 20),
68530
69256
  formatDate3(event.created_at)
68531
69257
  ].join(" ");
68532
69258
  console.log(row);
@@ -68579,7 +69305,7 @@ function formatDate3(dateStr) {
68579
69305
  return dateStr;
68580
69306
  }
68581
69307
  }
68582
- function padRight5(str, width) {
69308
+ function padRight6(str, width) {
68583
69309
  if (str.length >= width) return str;
68584
69310
  return str + " ".repeat(width - str.length);
68585
69311
  }
@@ -69391,11 +70117,11 @@ async function fetchGitHubKeys2(username) {
69391
70117
 
69392
70118
  // src/commands/agents.ts
69393
70119
  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");
70120
+ var import_node_fs10 = require("node:fs");
70121
+ var import_node_path10 = require("node:path");
69396
70122
  var import_yaml3 = require("yaml");
69397
70123
  function readYamlFile(filePath) {
69398
- const raw = (0, import_node_fs9.readFileSync)(filePath, "utf-8");
70124
+ const raw = (0, import_node_fs10.readFileSync)(filePath, "utf-8");
69399
70125
  const parsed = (0, import_yaml3.parse)(raw);
69400
70126
  if (!parsed || typeof parsed !== "object") {
69401
70127
  throw new Error(`Invalid YAML in ${filePath}`);
@@ -69407,9 +70133,9 @@ function resolveAgentsConfigPaths(repoRoot, manifest) {
69407
70133
  const agentsBlock = xEve["agents"] || {};
69408
70134
  const chatBlock = xEve["chat"] || {};
69409
70135
  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)(
70136
+ const agentsPath = (0, import_node_path10.resolve)(repoRoot, pickString(agentsBlock.config_path) ?? "agents/agents.yaml");
70137
+ const teamsPath = (0, import_node_path10.resolve)(repoRoot, pickString(agentsBlock.teams_path) ?? "agents/teams.yaml");
70138
+ const chatPath = (0, import_node_path10.resolve)(
69413
70139
  repoRoot,
69414
70140
  pickString(chatBlock.config_path) ?? pickString(manifestChat.config_path) ?? "agents/chat.yaml"
69415
70141
  );
@@ -69419,7 +70145,7 @@ function pickString(value) {
69419
70145
  return typeof value === "string" && value.trim().length > 0 ? value : void 0;
69420
70146
  }
69421
70147
  function ensureFileExists(path6, label) {
69422
- if (!(0, import_node_fs9.existsSync)(path6)) {
70148
+ if (!(0, import_node_fs10.existsSync)(path6)) {
69423
70149
  throw new Error(`Missing ${label} at ${path6}. Update manifest config_path or add the file.`);
69424
70150
  }
69425
70151
  return path6;
@@ -69434,9 +70160,9 @@ function isLocalApiUrl(apiUrl) {
69434
70160
  }
69435
70161
  }
69436
70162
  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)) {
70163
+ const eveDir = (0, import_node_path10.join)(repoRoot, ".eve");
70164
+ const manifestPath = (0, import_node_path10.join)(eveDir, "manifest.yaml");
70165
+ if ((0, import_node_fs10.existsSync)(manifestPath)) {
69440
70166
  const manifest = readYamlFile(manifestPath);
69441
70167
  const xEve = manifest["x-eve"] || manifest["x_eve"] || {};
69442
70168
  const policy = xEve["agents"] || null;
@@ -69486,16 +70212,16 @@ async function resolvePacksAndMerge(repoRoot, manifest, projectSlug) {
69486
70212
  }
69487
70213
  }
69488
70214
  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")) ?? {};
70215
+ if ((0, import_node_fs10.existsSync)(configPaths.agentsPath)) {
70216
+ const projectAgents = (0, import_yaml3.parse)((0, import_node_fs10.readFileSync)(configPaths.agentsPath, "utf-8")) ?? {};
69491
70217
  mergedAgents = mergeMapConfig(mergedAgents, projectAgents);
69492
70218
  }
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")) ?? {};
70219
+ if ((0, import_node_fs10.existsSync)(configPaths.teamsPath)) {
70220
+ const projectTeams = (0, import_yaml3.parse)((0, import_node_fs10.readFileSync)(configPaths.teamsPath, "utf-8")) ?? {};
69495
70221
  mergedTeams = mergeMapConfig(mergedTeams, projectTeams);
69496
70222
  }
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")) ?? {};
70223
+ if ((0, import_node_fs10.existsSync)(configPaths.chatPath)) {
70224
+ const projectChat = (0, import_yaml3.parse)((0, import_node_fs10.readFileSync)(configPaths.chatPath, "utf-8")) ?? {};
69499
70225
  mergedChat = mergeChatConfig(
69500
70226
  mergedChat,
69501
70227
  projectChat
@@ -69524,10 +70250,10 @@ async function resolvePacksAndMerge(repoRoot, manifest, projectSlug) {
69524
70250
  chat_hash: simpleHash(JSON.stringify(mergedChat))
69525
70251
  }
69526
70252
  };
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");
70253
+ const eveDir = (0, import_node_path10.join)(repoRoot, ".eve");
70254
+ (0, import_node_fs10.mkdirSync)(eveDir, { recursive: true });
70255
+ const lockfilePath = (0, import_node_path10.join)(eveDir, "packs.lock.yaml");
70256
+ (0, import_node_fs10.writeFileSync)(lockfilePath, (0, import_yaml3.stringify)(lockfile), "utf-8");
69531
70257
  console.log(` \u2713 Lockfile written: .eve/packs.lock.yaml`);
69532
70258
  const packRefs = resolvedPacks.map((p) => ({ id: p.id, source: p.source, ref: p.ref }));
69533
70259
  return {
@@ -69600,7 +70326,7 @@ async function handleAgents(subcommand, positionals, flags, context2) {
69600
70326
  const command = subcommand ?? "config";
69601
70327
  const json = Boolean(flags.json);
69602
70328
  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());
70329
+ const repoRoot = (0, import_node_path10.resolve)(getStringFlag(flags, ["repo-dir", "repo_dir", "dir", "path"]) ?? process.cwd());
69604
70330
  switch (command) {
69605
70331
  case "config": {
69606
70332
  const result = loadAgentsConfig(repoRoot);
@@ -69669,8 +70395,8 @@ async function handleAgents(subcommand, positionals, flags, context2) {
69669
70395
  if (dirty && !allowDirty) {
69670
70396
  throw new Error("Working tree is dirty. Commit changes or pass --allow-dirty to sync anyway.");
69671
70397
  }
69672
- const manifestPath = (0, import_node_path9.join)(repoRoot, ".eve", "manifest.yaml");
69673
- if (!(0, import_node_fs9.existsSync)(manifestPath)) {
70398
+ const manifestPath = (0, import_node_path10.join)(repoRoot, ".eve", "manifest.yaml");
70399
+ if (!(0, import_node_fs10.existsSync)(manifestPath)) {
69674
70400
  throw new Error(`Missing manifest at ${manifestPath}. Expected .eve/manifest.yaml.`);
69675
70401
  }
69676
70402
  const manifest = readYamlFile(manifestPath);
@@ -69699,9 +70425,9 @@ async function handleAgents(subcommand, positionals, flags, context2) {
69699
70425
  packRefs = packResult.packRefs;
69700
70426
  } else {
69701
70427
  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");
70428
+ agentsYaml = (0, import_node_fs10.readFileSync)(ensureFileExists(configPaths.agentsPath, "agents config"), "utf-8");
70429
+ teamsYaml = (0, import_node_fs10.readFileSync)(ensureFileExists(configPaths.teamsPath, "teams config"), "utf-8");
70430
+ chatYaml = (0, import_node_fs10.readFileSync)(ensureFileExists(configPaths.chatPath, "chat config"), "utf-8");
69705
70431
  }
69706
70432
  let gitSha;
69707
70433
  let branch;
@@ -69784,10 +70510,10 @@ function formatAgentRuntimeStatus(response, orgId) {
69784
70510
  const ageWidth = Math.max(3, ...response.pods.map((pod) => formatAgeSeconds(pod.last_heartbeat_at).length));
69785
70511
  console.log("");
69786
70512
  const header = [
69787
- padRight6("Pod", nameWidth),
69788
- padRight6("Status", statusWidth),
69789
- padRight6("Capacity", capacityWidth),
69790
- padRight6("Age", ageWidth),
70513
+ padRight7("Pod", nameWidth),
70514
+ padRight7("Status", statusWidth),
70515
+ padRight7("Capacity", capacityWidth),
70516
+ padRight7("Age", ageWidth),
69791
70517
  "Last Heartbeat"
69792
70518
  ].join(" ");
69793
70519
  console.log(header);
@@ -69795,15 +70521,15 @@ function formatAgentRuntimeStatus(response, orgId) {
69795
70521
  for (const pod of response.pods) {
69796
70522
  const age = formatAgeSeconds(pod.last_heartbeat_at);
69797
70523
  console.log([
69798
- padRight6(pod.pod_name, nameWidth),
69799
- padRight6(pod.status, statusWidth),
69800
- padRight6(String(pod.capacity), capacityWidth),
69801
- padRight6(age, ageWidth),
70524
+ padRight7(pod.pod_name, nameWidth),
70525
+ padRight7(pod.status, statusWidth),
70526
+ padRight7(String(pod.capacity), capacityWidth),
70527
+ padRight7(age, ageWidth),
69802
70528
  pod.last_heartbeat_at
69803
70529
  ].join(" "));
69804
70530
  }
69805
70531
  }
69806
- function padRight6(value, width) {
70532
+ function padRight7(value, width) {
69807
70533
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
69808
70534
  }
69809
70535
  function formatAgeSeconds(isoDate) {
@@ -70044,8 +70770,8 @@ function ensureSkillsSymlink2(projectRoot) {
70044
70770
  }
70045
70771
 
70046
70772
  // src/commands/release.ts
70047
- var import_node_fs10 = require("node:fs");
70048
- var import_node_path10 = require("node:path");
70773
+ var import_node_fs11 = require("node:fs");
70774
+ var import_node_path11 = require("node:path");
70049
70775
  async function handleRelease2(subcommand, positionals, flags, context2) {
70050
70776
  const json = Boolean(flags.json);
70051
70777
  switch (subcommand) {
@@ -70057,9 +70783,9 @@ async function handleRelease2(subcommand, positionals, flags, context2) {
70057
70783
  let projectId = typeof flags.project === "string" ? flags.project : context2.projectId;
70058
70784
  if (!projectId) {
70059
70785
  const dir = typeof flags.dir === "string" ? flags.dir : process.cwd();
70060
- const manifestPath = (0, import_node_path10.join)(dir, ".eve", "manifest.yaml");
70786
+ const manifestPath = (0, import_node_path11.join)(dir, ".eve", "manifest.yaml");
70061
70787
  try {
70062
- const yaml = (0, import_node_fs10.readFileSync)(manifestPath, "utf-8");
70788
+ const yaml = (0, import_node_fs11.readFileSync)(manifestPath, "utf-8");
70063
70789
  const projectMatch = yaml.match(/^project:\s*(\S+)/m);
70064
70790
  if (projectMatch) {
70065
70791
  projectId = projectMatch[1];
@@ -70108,8 +70834,8 @@ Make sure the release exists and the tag is correct.`
70108
70834
  }
70109
70835
 
70110
70836
  // src/commands/manifest.ts
70111
- var import_node_fs11 = require("node:fs");
70112
- var import_node_path11 = require("node:path");
70837
+ var import_node_fs12 = require("node:fs");
70838
+ var import_node_path12 = require("node:path");
70113
70839
  async function handleManifest(subcommand, positionals, flags, context2) {
70114
70840
  const json = Boolean(flags.json);
70115
70841
  switch (subcommand) {
@@ -70119,11 +70845,11 @@ async function handleManifest(subcommand, positionals, flags, context2) {
70119
70845
  const validateSecretsFlag = flags["validate-secrets"] ?? flags.validate_secrets;
70120
70846
  const validateSecrets = toBoolean(validateSecretsFlag) ?? false;
70121
70847
  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");
70848
+ const manifestPath = getStringFlag(flags, ["path"]) ?? (0, import_node_path12.join)(dir, ".eve", "manifest.yaml");
70123
70849
  let manifestYaml;
70124
70850
  if (!useLatest) {
70125
70851
  try {
70126
- manifestYaml = (0, import_node_fs11.readFileSync)(manifestPath, "utf-8");
70852
+ manifestYaml = (0, import_node_fs12.readFileSync)(manifestPath, "utf-8");
70127
70853
  } catch (error) {
70128
70854
  throw new Error(`Failed to read manifest at ${manifestPath}: ${error.message}`);
70129
70855
  }
@@ -70191,11 +70917,11 @@ async function handleManifest(subcommand, positionals, flags, context2) {
70191
70917
  }
70192
70918
 
70193
70919
  // src/commands/packs.ts
70194
- var import_node_fs12 = require("node:fs");
70195
- var import_node_path12 = require("node:path");
70920
+ var import_node_fs13 = require("node:fs");
70921
+ var import_node_path13 = require("node:path");
70196
70922
  var import_yaml4 = require("yaml");
70197
70923
  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());
70924
+ const repoRoot = (0, import_node_path13.resolve)(getStringFlag(flags, ["repo-dir", "repo_dir", "dir", "path"]) ?? process.cwd());
70199
70925
  switch (subcommand) {
70200
70926
  case "status": {
70201
70927
  printPacksStatus(repoRoot);
@@ -70211,14 +70937,14 @@ async function handlePacks(subcommand, _rest, flags, _context) {
70211
70937
  }
70212
70938
  }
70213
70939
  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)) {
70940
+ const lockfilePath = (0, import_node_path13.join)(repoRoot, ".eve", "packs.lock.yaml");
70941
+ const manifestPath = (0, import_node_path13.join)(repoRoot, ".eve", "manifest.yaml");
70942
+ if (!(0, import_node_fs13.existsSync)(lockfilePath)) {
70217
70943
  console.log("No lockfile found at .eve/packs.lock.yaml");
70218
70944
  console.log('Run "eve agents sync" to resolve packs and generate the lockfile.');
70219
70945
  return;
70220
70946
  }
70221
- const lockRaw = (0, import_node_fs12.readFileSync)(lockfilePath, "utf-8");
70947
+ const lockRaw = (0, import_node_fs13.readFileSync)(lockfilePath, "utf-8");
70222
70948
  const lock = (0, import_yaml4.parse)(lockRaw);
70223
70949
  if (!lock || !lock.packs) {
70224
70950
  console.log("Lockfile is empty or malformed.");
@@ -70234,16 +70960,16 @@ function printPacksStatus(repoRoot) {
70234
70960
  const idWidth = Math.max(4, ...lock.packs.map((p) => p.id.length));
70235
70961
  const sourceWidth = Math.max(6, ...lock.packs.map((p) => p.source.length));
70236
70962
  const header = [
70237
- padRight7("Pack", idWidth),
70238
- padRight7("Source", sourceWidth),
70963
+ padRight8("Pack", idWidth),
70964
+ padRight8("Source", sourceWidth),
70239
70965
  "Ref"
70240
70966
  ].join(" ");
70241
70967
  console.log(header);
70242
70968
  console.log("-".repeat(header.length + 12));
70243
70969
  for (const pack of lock.packs) {
70244
70970
  console.log([
70245
- padRight7(pack.id, idWidth),
70246
- padRight7(pack.source, sourceWidth),
70971
+ padRight8(pack.id, idWidth),
70972
+ padRight8(pack.source, sourceWidth),
70247
70973
  pack.ref.substring(0, 12)
70248
70974
  ].join(" "));
70249
70975
  }
@@ -70253,8 +70979,8 @@ function printPacksStatus(repoRoot) {
70253
70979
  console.log(` Agents: ${lock.effective.agents_count}`);
70254
70980
  console.log(` Teams: ${lock.effective.teams_count}`);
70255
70981
  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");
70982
+ if ((0, import_node_fs13.existsSync)(manifestPath)) {
70983
+ const manifestRaw = (0, import_node_fs13.readFileSync)(manifestPath, "utf-8");
70258
70984
  const manifest = (0, import_yaml4.parse)(manifestRaw);
70259
70985
  if (manifest) {
70260
70986
  const xEve = manifest["x-eve"] ?? manifest["x_eve"] ?? {};
@@ -70299,12 +71025,12 @@ function detectDrift(lock, manifestPacks) {
70299
71025
  }
70300
71026
  function printPacksResolve(repoRoot, dryRun) {
70301
71027
  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)) {
71028
+ const lockfilePath = (0, import_node_path13.join)(repoRoot, ".eve", "packs.lock.yaml");
71029
+ const manifestPath = (0, import_node_path13.join)(repoRoot, ".eve", "manifest.yaml");
71030
+ if (!(0, import_node_fs13.existsSync)(manifestPath)) {
70305
71031
  throw new Error("No manifest found at .eve/manifest.yaml");
70306
71032
  }
70307
- const manifestRaw = (0, import_node_fs12.readFileSync)(manifestPath, "utf-8");
71033
+ const manifestRaw = (0, import_node_fs13.readFileSync)(manifestPath, "utf-8");
70308
71034
  const manifest = (0, import_yaml4.parse)(manifestRaw);
70309
71035
  if (!manifest) {
70310
71036
  throw new Error("Manifest is empty or malformed.");
@@ -70322,8 +71048,8 @@ function printPacksResolve(repoRoot, dryRun) {
70322
71048
  const refShort = mp.ref ? mp.ref.substring(0, 12) : "(local)";
70323
71049
  console.log(` - ${mp.source} @ ${refShort}`);
70324
71050
  }
70325
- if ((0, import_node_fs12.existsSync)(lockfilePath)) {
70326
- const lockRaw = (0, import_node_fs12.readFileSync)(lockfilePath, "utf-8");
71051
+ if ((0, import_node_fs13.existsSync)(lockfilePath)) {
71052
+ const lockRaw = (0, import_node_fs13.readFileSync)(lockfilePath, "utf-8");
70327
71053
  const lock = (0, import_yaml4.parse)(lockRaw);
70328
71054
  if (lock?.packs) {
70329
71055
  const drift = detectDrift(lock, manifestPacks);
@@ -70352,7 +71078,7 @@ function printPacksResolve(repoRoot, dryRun) {
70352
71078
  console.log("");
70353
71079
  console.log("This will resolve packs, merge configs, write the lockfile, and sync to the API.");
70354
71080
  }
70355
- function padRight7(value, width) {
71081
+ function padRight8(value, width) {
70356
71082
  return value.length >= width ? value : `${value}${" ".repeat(width - value.length)}`;
70357
71083
  }
70358
71084
 
@@ -71178,8 +71904,8 @@ Cursor: ${result.cursor}`);
71178
71904
  }
71179
71905
 
71180
71906
  // src/commands/migrate.ts
71181
- var import_node_fs13 = require("node:fs");
71182
- var import_node_path13 = require("node:path");
71907
+ var import_node_fs14 = require("node:fs");
71908
+ var import_node_path14 = require("node:path");
71183
71909
  var import_yaml5 = require("yaml");
71184
71910
  async function handleMigrate2(subcommand, _rest, _flags) {
71185
71911
  switch (subcommand) {
@@ -71199,12 +71925,12 @@ async function migrateSkillsToPacks() {
71199
71925
  if (!repoRoot) {
71200
71926
  throw new Error("Not in a git repository. Run this from your project root.");
71201
71927
  }
71202
- const skillsTxtPath = (0, import_node_path13.join)(repoRoot, "skills.txt");
71203
- if (!(0, import_node_fs13.existsSync)(skillsTxtPath)) {
71928
+ const skillsTxtPath = (0, import_node_path14.join)(repoRoot, "skills.txt");
71929
+ if (!(0, import_node_fs14.existsSync)(skillsTxtPath)) {
71204
71930
  console.log("No skills.txt found at repository root. Nothing to migrate.");
71205
71931
  return;
71206
71932
  }
71207
- const content = (0, import_node_fs13.readFileSync)(skillsTxtPath, "utf-8");
71933
+ const content = (0, import_node_fs14.readFileSync)(skillsTxtPath, "utf-8");
71208
71934
  const lines = content.split("\n");
71209
71935
  const localSources = [];
71210
71936
  const remoteSources = [];
@@ -71313,9 +72039,9 @@ async function handleList8(flags, context2, json) {
71313
72039
  const nameWidth = Math.max(12, ...response.managed.map((m) => m.display_name.length));
71314
72040
  const providerWidth = Math.max(8, ...response.managed.map((m) => m.inference_provider.length));
71315
72041
  const header = [
71316
- padRight8("Model Spec", specWidth),
71317
- padRight8("Display Name", nameWidth),
71318
- padRight8("Provider", providerWidth),
72042
+ padRight9("Model Spec", specWidth),
72043
+ padRight9("Display Name", nameWidth),
72044
+ padRight9("Provider", providerWidth),
71319
72045
  "Capabilities"
71320
72046
  ].join(" ");
71321
72047
  console.log(header);
@@ -71326,9 +72052,9 @@ async function handleList8(flags, context2, json) {
71326
72052
  if (model.capabilities.tool_calling) caps.push("tools");
71327
72053
  if (model.capabilities.reasoning) caps.push("reason");
71328
72054
  const row = [
71329
- padRight8(model.model_spec, specWidth),
71330
- padRight8(model.display_name, nameWidth),
71331
- padRight8(model.inference_provider, providerWidth),
72055
+ padRight9(model.model_spec, specWidth),
72056
+ padRight9(model.display_name, nameWidth),
72057
+ padRight9(model.inference_provider, providerWidth),
71332
72058
  caps.join(", ")
71333
72059
  ].join(" ");
71334
72060
  console.log(row);
@@ -71338,16 +72064,95 @@ async function handleList8(flags, context2, json) {
71338
72064
  console.log("");
71339
72065
  console.log('Usage: Set harness_options.model to the model spec (e.g. "managed/deepseek-r1")');
71340
72066
  }
71341
- function padRight8(str, width) {
72067
+ function padRight9(str, width) {
71342
72068
  if (str.length >= width) return str;
71343
72069
  return str + " ".repeat(width - str.length);
71344
72070
  }
71345
72071
 
71346
72072
  // src/commands/access.ts
71347
- var import_node_fs14 = require("node:fs");
71348
- var import_node_path14 = require("node:path");
72073
+ var import_node_fs15 = require("node:fs");
72074
+ var import_node_path15 = require("node:path");
71349
72075
  var readline3 = __toESM(require("node:readline/promises"));
71350
72076
  var import_yaml6 = require("yaml");
72077
+ function resolvePrincipalSelection(flags, options) {
72078
+ const userId = getStringFlag(flags, ["user"]);
72079
+ const spId = getStringFlag(flags, ["service-principal", "sp"]);
72080
+ const groupId = options?.allowGroup ? getStringFlag(flags, ["group"]) : void 0;
72081
+ const selected = [
72082
+ userId ? "user" : null,
72083
+ spId ? "service_principal" : null,
72084
+ groupId ? "group" : null
72085
+ ].filter(Boolean);
72086
+ if (selected.length === 0) {
72087
+ const groupHint = options?.allowGroup ? " or --group <group_id>" : "";
72088
+ throw new Error(`--user <user_id> or --service-principal <sp_id>${groupHint} is required`);
72089
+ }
72090
+ if (selected.length > 1) {
72091
+ throw new Error("Specify exactly one principal selector: --user, --service-principal, or --group");
72092
+ }
72093
+ if (userId) {
72094
+ return { principalType: "user", principalId: userId };
72095
+ }
72096
+ if (spId) {
72097
+ return { principalType: "service_principal", principalId: spId };
72098
+ }
72099
+ return { principalType: "group", principalId: groupId };
72100
+ }
72101
+ function parseScopeJsonFlag(flags) {
72102
+ const raw = getStringFlag(flags, ["scope-json"]);
72103
+ if (!raw) {
72104
+ return void 0;
72105
+ }
72106
+ try {
72107
+ const parsed = JSON.parse(raw);
72108
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
72109
+ throw new Error("must be a JSON object");
72110
+ }
72111
+ return parsed;
72112
+ } catch (error) {
72113
+ const message = error instanceof Error ? error.message : String(error);
72114
+ throw new Error(`Invalid --scope-json: ${message}`);
72115
+ }
72116
+ }
72117
+ function normalizeBindingScope(scope) {
72118
+ if (!scope) {
72119
+ return void 0;
72120
+ }
72121
+ const normalized = {};
72122
+ if (scope.orgfs) {
72123
+ const allow = [...new Set(scope.orgfs.allow_prefixes ?? [])].sort();
72124
+ const readOnly = [...new Set(scope.orgfs.read_only_prefixes ?? [])].sort();
72125
+ if (allow.length > 0 || readOnly.length > 0) {
72126
+ normalized.orgfs = {};
72127
+ if (allow.length > 0) normalized.orgfs.allow_prefixes = allow;
72128
+ if (readOnly.length > 0) normalized.orgfs.read_only_prefixes = readOnly;
72129
+ }
72130
+ }
72131
+ if (scope.orgdocs) {
72132
+ const allow = [...new Set(scope.orgdocs.allow_prefixes ?? [])].sort();
72133
+ const readOnly = [...new Set(scope.orgdocs.read_only_prefixes ?? [])].sort();
72134
+ if (allow.length > 0 || readOnly.length > 0) {
72135
+ normalized.orgdocs = {};
72136
+ if (allow.length > 0) normalized.orgdocs.allow_prefixes = allow;
72137
+ if (readOnly.length > 0) normalized.orgdocs.read_only_prefixes = readOnly;
72138
+ }
72139
+ }
72140
+ if (scope.envdb) {
72141
+ const schemas = [...new Set(scope.envdb.schemas ?? [])].sort();
72142
+ const tables = [...new Set(scope.envdb.tables ?? [])].sort();
72143
+ if (schemas.length > 0 || tables.length > 0) {
72144
+ normalized.envdb = {};
72145
+ if (schemas.length > 0) normalized.envdb.schemas = schemas;
72146
+ if (tables.length > 0) normalized.envdb.tables = tables;
72147
+ }
72148
+ }
72149
+ return Object.keys(normalized).length > 0 ? normalized : void 0;
72150
+ }
72151
+ function scopeEquals(left, right) {
72152
+ const normalizedLeft = normalizeBindingScope(left);
72153
+ const normalizedRight = normalizeBindingScope(right);
72154
+ return JSON.stringify(normalizedLeft ?? null) === JSON.stringify(normalizedRight ?? null);
72155
+ }
71351
72156
  async function handleAccess(subcommand, rest, flags, context2) {
71352
72157
  const json = Boolean(flags.json);
71353
72158
  switch (subcommand) {
@@ -71363,6 +72168,10 @@ async function handleAccess(subcommand, rest, flags, context2) {
71363
72168
  return handleUnbind(flags, context2, json);
71364
72169
  case "bindings":
71365
72170
  return handleBindings(rest[0], flags, context2, json);
72171
+ case "groups":
72172
+ return handleGroups(rest[0], rest.slice(1), flags, context2, json);
72173
+ case "memberships":
72174
+ return handleMemberships(flags, context2, json);
71366
72175
  case "validate":
71367
72176
  return handleValidate(flags, json);
71368
72177
  case "plan":
@@ -71371,38 +72180,44 @@ async function handleAccess(subcommand, rest, flags, context2) {
71371
72180
  return handleSync(flags, context2, json);
71372
72181
  default:
71373
72182
  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)"
72183
+ "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
72184
  );
71376
72185
  }
71377
72186
  }
71378
72187
  async function handleCan(flags, context2, json) {
71379
72188
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
71380
- const userId = getStringFlag(flags, ["user"]);
71381
- const spId = getStringFlag(flags, ["service-principal", "sp"]);
71382
72189
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
71383
72190
  const permission = getStringFlag(flags, ["permission"]);
72191
+ const resourceType = getStringFlag(flags, ["resource-type"]);
72192
+ const resourceId = getStringFlag(flags, ["resource", "resource-id"]);
72193
+ const action = getStringFlag(flags, ["action"]);
71384
72194
  if (!orgId) {
71385
72195
  throw new Error("--org is required (or set a default org in your profile)");
71386
72196
  }
71387
72197
  if (!permission) {
71388
72198
  throw new Error("--permission is required");
71389
72199
  }
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");
72200
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
72201
+ if ((resourceType || action) && !resourceId) {
72202
+ throw new Error("--resource is required when --resource-type or --action is provided");
71395
72203
  }
71396
- const principalType = userId ? "user" : "service_principal";
71397
- const principalId = userId ?? spId;
71398
72204
  const params = new URLSearchParams({
71399
- principal_type: principalType,
71400
- principal_id: principalId,
72205
+ principal_type: principal.principalType,
72206
+ principal_id: principal.principalId,
71401
72207
  permission
71402
72208
  });
71403
72209
  if (projectId) {
71404
72210
  params.set("project_id", projectId);
71405
72211
  }
72212
+ if (resourceType) {
72213
+ params.set("resource_type", resourceType);
72214
+ }
72215
+ if (resourceId) {
72216
+ params.set("resource_id", resourceId);
72217
+ }
72218
+ if (action) {
72219
+ params.set("action", action);
72220
+ }
71406
72221
  const response = await requestJson(
71407
72222
  context2,
71408
72223
  `/orgs/${orgId}/access/can?${params.toString()}`
@@ -71413,35 +72228,47 @@ async function handleCan(flags, context2, json) {
71413
72228
  }
71414
72229
  const label = response.allowed ? "ALLOWED" : "DENIED";
71415
72230
  console.log(`${label} (source: ${response.source})`);
72231
+ if (response.resource) {
72232
+ const resourceStatus = response.resource.scope_matched ? "scope match" : "scope denied";
72233
+ console.log(
72234
+ `Resource: ${response.resource.type}:${response.resource.id} [${response.resource.action}] (${resourceStatus})`
72235
+ );
72236
+ }
71416
72237
  }
71417
72238
  async function handleExplain(flags, context2, json) {
71418
72239
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
71419
- const userId = getStringFlag(flags, ["user"]);
71420
- const spId = getStringFlag(flags, ["service-principal", "sp"]);
71421
72240
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
71422
72241
  const permission = getStringFlag(flags, ["permission"]);
72242
+ const resourceType = getStringFlag(flags, ["resource-type"]);
72243
+ const resourceId = getStringFlag(flags, ["resource", "resource-id"]);
72244
+ const action = getStringFlag(flags, ["action"]);
71423
72245
  if (!orgId) {
71424
72246
  throw new Error("--org is required (or set a default org in your profile)");
71425
72247
  }
71426
72248
  if (!permission) {
71427
72249
  throw new Error("--permission is required");
71428
72250
  }
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");
72251
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
72252
+ if ((resourceType || action) && !resourceId) {
72253
+ throw new Error("--resource is required when --resource-type or --action is provided");
71434
72254
  }
71435
- const principalType = userId ? "user" : "service_principal";
71436
- const principalId = userId ?? spId;
71437
72255
  const params = new URLSearchParams({
71438
- principal_type: principalType,
71439
- principal_id: principalId,
72256
+ principal_type: principal.principalType,
72257
+ principal_id: principal.principalId,
71440
72258
  permission
71441
72259
  });
71442
72260
  if (projectId) {
71443
72261
  params.set("project_id", projectId);
71444
72262
  }
72263
+ if (resourceType) {
72264
+ params.set("resource_type", resourceType);
72265
+ }
72266
+ if (resourceId) {
72267
+ params.set("resource_id", resourceId);
72268
+ }
72269
+ if (action) {
72270
+ params.set("action", action);
72271
+ }
71445
72272
  const response = await requestJson(
71446
72273
  context2,
71447
72274
  `/orgs/${orgId}/access/explain?${params.toString()}`
@@ -71452,13 +72279,20 @@ async function handleExplain(flags, context2, json) {
71452
72279
  }
71453
72280
  console.log(`Permission: ${response.permission}`);
71454
72281
  console.log(`Result: ${response.result}`);
72282
+ if (response.resource) {
72283
+ const resourceStatus = response.resource.scope_matched ? "scope match" : "scope denied";
72284
+ console.log(
72285
+ `Resource: ${response.resource.type}:${response.resource.id} [${response.resource.action}] (${resourceStatus})`
72286
+ );
72287
+ }
71455
72288
  if (response.grants.length > 0) {
71456
72289
  console.log("Grants found:");
71457
72290
  for (const grant of response.grants) {
71458
72291
  const roleLabel = grant.role ? `: ${grant.role}` : "";
71459
72292
  const permCount = grant.permissions.length;
71460
72293
  const status = grant.has_permission ? "has permission" : `missing ${response.permission}`;
71461
- console.log(` - ${grant.source}${roleLabel} -> [${permCount} permissions] (${status})`);
72294
+ const scopeSuffix = grant.scope_match === void 0 ? "" : grant.scope_match ? " [scope:match]" : ` [scope:deny${grant.scope_reason ? `: ${grant.scope_reason}` : ""}]`;
72295
+ console.log(` - ${grant.source}${roleLabel} -> [${permCount} permissions] (${status})${scopeSuffix}`);
71462
72296
  }
71463
72297
  } else {
71464
72298
  console.log("Grants found: none");
@@ -71638,32 +72472,27 @@ async function handleRoles(action, positionals, flags, context2, json) {
71638
72472
  }
71639
72473
  async function handleBind(flags, context2, json) {
71640
72474
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
71641
- const userId = getStringFlag(flags, ["user"]);
71642
- const spId = getStringFlag(flags, ["service-principal", "sp"]);
71643
72475
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
71644
72476
  const roleName = getStringFlag(flags, ["role"]);
72477
+ const scopeJson = parseScopeJsonFlag(flags);
71645
72478
  if (!orgId) {
71646
72479
  throw new Error("--org is required (or set a default org in your profile)");
71647
72480
  }
71648
72481
  if (!roleName) {
71649
72482
  throw new Error("--role <role_name> is required");
71650
72483
  }
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;
72484
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
71659
72485
  const body = {
71660
72486
  role_name: roleName,
71661
- principal_type: principalType,
71662
- principal_id: principalId
72487
+ principal_type: principal.principalType,
72488
+ principal_id: principal.principalId
71663
72489
  };
71664
72490
  if (projectId) {
71665
72491
  body.project_id = projectId;
71666
72492
  }
72493
+ if (scopeJson) {
72494
+ body.scope_json = scopeJson;
72495
+ }
71667
72496
  const response = await requestJson(
71668
72497
  context2,
71669
72498
  `/orgs/${orgId}/access/bindings`,
@@ -71674,12 +72503,10 @@ async function handleBind(flags, context2, json) {
71674
72503
  return;
71675
72504
  }
71676
72505
  const scopeLabel = response.project_id ? `project ${response.project_id}` : `org ${orgId}`;
71677
- console.log(`Bound role '${response.role_name}' to ${principalType} ${principalId} on ${scopeLabel}`);
72506
+ console.log(`Bound role '${response.role_name}' to ${principal.principalType} ${principal.principalId} on ${scopeLabel}`);
71678
72507
  }
71679
72508
  async function handleUnbind(flags, context2, json) {
71680
72509
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
71681
- const userId = getStringFlag(flags, ["user"]);
71682
- const spId = getStringFlag(flags, ["service-principal", "sp"]);
71683
72510
  const projectId = getStringFlag(flags, ["project"]) ?? context2.projectId;
71684
72511
  const roleName = getStringFlag(flags, ["role"]);
71685
72512
  if (!orgId) {
@@ -71688,17 +72515,10 @@ async function handleUnbind(flags, context2, json) {
71688
72515
  if (!roleName) {
71689
72516
  throw new Error("--role <role_name> is required");
71690
72517
  }
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;
72518
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
71699
72519
  const params = new URLSearchParams({
71700
- principal_type: principalType,
71701
- principal_id: principalId
72520
+ principal_type: principal.principalType,
72521
+ principal_id: principal.principalId
71702
72522
  });
71703
72523
  if (projectId) {
71704
72524
  params.set("project_id", projectId);
@@ -71710,14 +72530,14 @@ async function handleUnbind(flags, context2, json) {
71710
72530
  const bindings = unwrapListResponse(bindingsResponse);
71711
72531
  const matching = bindings.find((b) => {
71712
72532
  if (b.role_name !== roleName) return false;
71713
- if (b.principal_type !== principalType) return false;
71714
- if (b.principal_id !== principalId) return false;
72533
+ if (b.principal_type !== principal.principalType) return false;
72534
+ if (b.principal_id !== principal.principalId) return false;
71715
72535
  if (projectId) return b.project_id === projectId;
71716
72536
  return b.project_id === null;
71717
72537
  });
71718
72538
  if (!matching) {
71719
72539
  throw new Error(
71720
- `No binding found for role '${roleName}' on ${principalType} ${principalId}` + (projectId ? ` in project ${projectId}` : " at org level")
72540
+ `No binding found for role '${roleName}' on ${principal.principalType} ${principal.principalId}` + (projectId ? ` in project ${projectId}` : " at org level")
71721
72541
  );
71722
72542
  }
71723
72543
  await requestJson(
@@ -71730,7 +72550,7 @@ async function handleUnbind(flags, context2, json) {
71730
72550
  return;
71731
72551
  }
71732
72552
  const scopeLabel = projectId ? `project ${projectId}` : `org ${orgId}`;
71733
- console.log(`Unbound role '${roleName}' from ${principalType} ${principalId} on ${scopeLabel}`);
72553
+ console.log(`Unbound role '${roleName}' from ${principal.principalType} ${principal.principalId} on ${scopeLabel}`);
71734
72554
  }
71735
72555
  async function handleBindings(action, flags, context2, json) {
71736
72556
  if (action && action !== "list") {
@@ -71762,16 +72582,262 @@ async function handleBindings(action, flags, context2, json) {
71762
72582
  console.log(`${b.role_name} -> ${b.principal_type} ${b.principal_id} (${scope})`);
71763
72583
  }
71764
72584
  }
72585
+ async function handleGroups(action, positionals, flags, context2, json) {
72586
+ const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
72587
+ if (!orgId) {
72588
+ throw new Error("--org is required (or set a default org in your profile)");
72589
+ }
72590
+ if (action === "members") {
72591
+ return handleGroupMembers(positionals[0], positionals.slice(1), flags, context2, json);
72592
+ }
72593
+ switch (action) {
72594
+ case "create": {
72595
+ const name = positionals[0] ?? getStringFlag(flags, ["name"]);
72596
+ const slug = getStringFlag(flags, ["slug"]);
72597
+ const description = getStringFlag(flags, ["description"]);
72598
+ if (!name) {
72599
+ throw new Error("Usage: eve access groups create <name> --org <org_id> [--slug <slug>] [--description <text>]");
72600
+ }
72601
+ const response = await requestJson(context2, `/orgs/${orgId}/access/groups`, {
72602
+ method: "POST",
72603
+ body: { name, slug, description }
72604
+ });
72605
+ if (json) {
72606
+ outputJson(response, json);
72607
+ return;
72608
+ }
72609
+ console.log(`Created group '${response.name}' (${response.id})`);
72610
+ console.log(` Slug: ${response.slug}`);
72611
+ if (response.description) {
72612
+ console.log(` Description: ${response.description}`);
72613
+ }
72614
+ return;
72615
+ }
72616
+ case "list": {
72617
+ const listResponse = await requestJson(
72618
+ context2,
72619
+ `/orgs/${orgId}/access/groups`
72620
+ );
72621
+ const groups = unwrapListResponse(listResponse);
72622
+ if (json) {
72623
+ outputJson({ data: groups }, json);
72624
+ return;
72625
+ }
72626
+ if (groups.length === 0) {
72627
+ console.log("No access groups found.");
72628
+ return;
72629
+ }
72630
+ for (const group of groups) {
72631
+ const desc = group.description ? ` - ${group.description}` : "";
72632
+ console.log(`${group.slug} (${group.id})${desc}`);
72633
+ }
72634
+ return;
72635
+ }
72636
+ case "show": {
72637
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72638
+ if (!group) {
72639
+ throw new Error("Usage: eve access groups show <group_id_or_slug> --org <org_id>");
72640
+ }
72641
+ const response = await requestJson(
72642
+ context2,
72643
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}`
72644
+ );
72645
+ if (json) {
72646
+ outputJson(response, json);
72647
+ return;
72648
+ }
72649
+ console.log(`Name: ${response.name}`);
72650
+ console.log(`ID: ${response.id}`);
72651
+ console.log(`Slug: ${response.slug}`);
72652
+ if (response.description) {
72653
+ console.log(`Description: ${response.description}`);
72654
+ }
72655
+ console.log(`Created: ${response.created_at}`);
72656
+ console.log(`Updated: ${response.updated_at}`);
72657
+ return;
72658
+ }
72659
+ case "update": {
72660
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72661
+ if (!group) {
72662
+ throw new Error("Usage: eve access groups update <group_id_or_slug> --org <org_id> [--name <name>] [--slug <slug>] [--description <text>]");
72663
+ }
72664
+ const name = getStringFlag(flags, ["name"]);
72665
+ const slug = getStringFlag(flags, ["slug"]);
72666
+ const description = getStringFlag(flags, ["description"]);
72667
+ if (name === void 0 && slug === void 0 && description === void 0) {
72668
+ throw new Error("Nothing to update. Use --name, --slug, or --description");
72669
+ }
72670
+ const response = await requestJson(
72671
+ context2,
72672
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}`,
72673
+ {
72674
+ method: "PATCH",
72675
+ body: {
72676
+ name,
72677
+ slug,
72678
+ description
72679
+ }
72680
+ }
72681
+ );
72682
+ if (json) {
72683
+ outputJson(response, json);
72684
+ return;
72685
+ }
72686
+ console.log(`Updated group '${response.name}' (${response.id})`);
72687
+ console.log(` Slug: ${response.slug}`);
72688
+ if (response.description) {
72689
+ console.log(` Description: ${response.description}`);
72690
+ }
72691
+ return;
72692
+ }
72693
+ case "delete": {
72694
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72695
+ if (!group) {
72696
+ throw new Error("Usage: eve access groups delete <group_id_or_slug> --org <org_id>");
72697
+ }
72698
+ await requestJson(context2, `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}`, {
72699
+ method: "DELETE"
72700
+ });
72701
+ if (json) {
72702
+ outputJson({ deleted: true, group }, json);
72703
+ return;
72704
+ }
72705
+ console.log(`Deleted group '${group}'`);
72706
+ return;
72707
+ }
72708
+ default:
72709
+ throw new Error(
72710
+ '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'
72711
+ );
72712
+ }
72713
+ }
72714
+ async function handleGroupMembers(action, positionals, flags, context2, json) {
72715
+ const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
72716
+ if (!orgId) {
72717
+ throw new Error("--org is required (or set a default org in your profile)");
72718
+ }
72719
+ switch (action) {
72720
+ case "add": {
72721
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72722
+ if (!group) {
72723
+ throw new Error("Usage: eve access groups members add <group> --org <org_id> (--user <user_id> | --service-principal <sp_id>)");
72724
+ }
72725
+ const principal = resolvePrincipalSelection(flags);
72726
+ const response = await requestJson(
72727
+ context2,
72728
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}/members`,
72729
+ {
72730
+ method: "POST",
72731
+ body: {
72732
+ principal_type: principal.principalType,
72733
+ principal_id: principal.principalId
72734
+ }
72735
+ }
72736
+ );
72737
+ if (json) {
72738
+ outputJson(response, json);
72739
+ return;
72740
+ }
72741
+ console.log(`Added ${response.principal_type} ${response.principal_id} to group ${group}`);
72742
+ return;
72743
+ }
72744
+ case "list": {
72745
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72746
+ if (!group) {
72747
+ throw new Error("Usage: eve access groups members list <group> --org <org_id>");
72748
+ }
72749
+ const membersResponse = await requestJson(
72750
+ context2,
72751
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}/members`
72752
+ );
72753
+ const members = unwrapListResponse(membersResponse);
72754
+ if (json) {
72755
+ outputJson({ data: members }, json);
72756
+ return;
72757
+ }
72758
+ if (members.length === 0) {
72759
+ console.log("No group members found.");
72760
+ return;
72761
+ }
72762
+ for (const member of members) {
72763
+ console.log(`${member.principal_type} ${member.principal_id}`);
72764
+ }
72765
+ return;
72766
+ }
72767
+ case "remove": {
72768
+ const group = positionals[0] ?? getStringFlag(flags, ["group"]);
72769
+ if (!group) {
72770
+ throw new Error("Usage: eve access groups members remove <group> --org <org_id> (--user <user_id> | --service-principal <sp_id>)");
72771
+ }
72772
+ const principal = resolvePrincipalSelection(flags);
72773
+ await requestJson(
72774
+ context2,
72775
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group)}/members/${principal.principalType}/${principal.principalId}`,
72776
+ { method: "DELETE" }
72777
+ );
72778
+ if (json) {
72779
+ outputJson({ removed: true, group, principal: principal.principalId }, json);
72780
+ return;
72781
+ }
72782
+ console.log(`Removed ${principal.principalType} ${principal.principalId} from group ${group}`);
72783
+ return;
72784
+ }
72785
+ default:
72786
+ throw new Error(
72787
+ "Usage: eve access groups members <add|list|remove> <group> --org <org_id> [--user <id> | --service-principal <id>]"
72788
+ );
72789
+ }
72790
+ }
72791
+ async function handleMemberships(flags, context2, json) {
72792
+ const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
72793
+ if (!orgId) {
72794
+ throw new Error("--org is required (or set a default org in your profile)");
72795
+ }
72796
+ const principal = resolvePrincipalSelection(flags, { allowGroup: true });
72797
+ const response = await requestJson(
72798
+ context2,
72799
+ `/orgs/${orgId}/access/principals/${principal.principalType}/${principal.principalId}/memberships`
72800
+ );
72801
+ if (json) {
72802
+ outputJson(response, json);
72803
+ return;
72804
+ }
72805
+ console.log(`Principal: ${response.principal_type} ${response.principal_id}`);
72806
+ if (response.base.org_role) {
72807
+ console.log(`Org role: ${response.base.org_role}`);
72808
+ }
72809
+ if (response.base.project_roles.length > 0) {
72810
+ console.log(`Project roles: ${response.base.project_roles.length}`);
72811
+ }
72812
+ if (response.groups.length > 0) {
72813
+ console.log(`Groups: ${response.groups.map((group) => group.slug).join(", ")}`);
72814
+ } else {
72815
+ console.log("Groups: none");
72816
+ }
72817
+ console.log(`Effective permissions: ${response.effective_permissions.length}`);
72818
+ if (response.effective_scopes.orgfs.allow_prefixes.length > 0) {
72819
+ console.log(`orgfs allow: ${response.effective_scopes.orgfs.allow_prefixes.join(", ")}`);
72820
+ }
72821
+ if (response.effective_scopes.orgdocs.allow_prefixes.length > 0) {
72822
+ console.log(`orgdocs allow: ${response.effective_scopes.orgdocs.allow_prefixes.join(", ")}`);
72823
+ }
72824
+ if (response.effective_scopes.envdb.schemas.length > 0 || response.effective_scopes.envdb.tables.length > 0) {
72825
+ const schemas = response.effective_scopes.envdb.schemas.join(", ") || "-";
72826
+ const tables = response.effective_scopes.envdb.tables.join(", ") || "-";
72827
+ console.log(`envdb schemas: ${schemas}`);
72828
+ console.log(`envdb tables: ${tables}`);
72829
+ }
72830
+ }
71765
72831
  var DEFAULT_ACCESS_YAML = ".eve/access.yaml";
71766
72832
  function resolveFilePath(flags) {
71767
72833
  const filePath = getStringFlag(flags, ["file", "f"]) ?? DEFAULT_ACCESS_YAML;
71768
- return (0, import_node_path14.resolve)(process.cwd(), filePath);
72834
+ return (0, import_node_path15.resolve)(process.cwd(), filePath);
71769
72835
  }
71770
72836
  function loadAccessYaml(filePath) {
71771
- if (!(0, import_node_fs14.existsSync)(filePath)) {
72837
+ if (!(0, import_node_fs15.existsSync)(filePath)) {
71772
72838
  throw new Error(`File not found: ${filePath}`);
71773
72839
  }
71774
- const raw = (0, import_node_fs14.readFileSync)(filePath, "utf-8");
72840
+ const raw = (0, import_node_fs15.readFileSync)(filePath, "utf-8");
71775
72841
  let parsed;
71776
72842
  try {
71777
72843
  parsed = (0, import_yaml6.parse)(raw);
@@ -71789,10 +72855,62 @@ ${issues.join("\n")}`);
71789
72855
  }
71790
72856
  return result.data;
71791
72857
  }
72858
+ function permissionScopeType(permission) {
72859
+ if (permission.startsWith("orgfs:")) return "orgfs";
72860
+ if (permission.startsWith("orgdocs:")) return "orgdocs";
72861
+ if (permission.startsWith("envdb:")) return "envdb";
72862
+ return null;
72863
+ }
72864
+ function hasPrefixScope(scope, resourceType, requireWritable) {
72865
+ const resourceScope = scope?.[resourceType];
72866
+ if (!resourceScope) {
72867
+ return false;
72868
+ }
72869
+ const allowCount = resourceScope.allow_prefixes?.length ?? 0;
72870
+ const readOnlyCount = resourceScope.read_only_prefixes?.length ?? 0;
72871
+ if (requireWritable) {
72872
+ return allowCount > 0;
72873
+ }
72874
+ return allowCount > 0 || readOnlyCount > 0;
72875
+ }
72876
+ function hasEnvDbScope(scope) {
72877
+ const envdb = scope?.envdb;
72878
+ if (!envdb) {
72879
+ return false;
72880
+ }
72881
+ return (envdb.schemas?.length ?? 0) > 0 || (envdb.tables?.length ?? 0) > 0;
72882
+ }
72883
+ function groupDefaultName(slug) {
72884
+ return slug.split("-").filter(Boolean).map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join(" ");
72885
+ }
72886
+ function declaredMemberKey(member) {
72887
+ return `${member.type}:${member.id}`;
72888
+ }
72889
+ function apiMemberKey(member) {
72890
+ return `${member.principal_type}:${member.principal_id}`;
72891
+ }
72892
+ function bindingIdentityKey(roleName, principalType, principalId, projectId) {
72893
+ const scope = projectId ?? "org";
72894
+ return `${roleName}|${principalType}|${principalId}|${scope}`;
72895
+ }
72896
+ function bindingMatchKey(roleName, principalType, principalId, projectId, scope) {
72897
+ const normalizedScope = normalizeBindingScope(scope);
72898
+ return `${bindingIdentityKey(roleName, principalType, principalId, projectId)}|${JSON.stringify(normalizedScope ?? null)}`;
72899
+ }
72900
+ function formatBindingScope(scope) {
72901
+ const normalized = normalizeBindingScope(scope);
72902
+ if (!normalized) {
72903
+ return "(none)";
72904
+ }
72905
+ return JSON.stringify(normalized);
72906
+ }
71792
72907
  function semanticValidate(yaml) {
71793
72908
  const errors = [];
71794
- const roleNames = new Set(Object.keys(yaml.access.roles ?? {}));
71795
- for (const [name, role] of Object.entries(yaml.access.roles ?? {})) {
72909
+ const roles = yaml.access.roles ?? {};
72910
+ const groups = yaml.access.groups ?? {};
72911
+ const roleNames = new Set(Object.keys(roles));
72912
+ const groupSlugs = new Set(Object.keys(groups));
72913
+ for (const [name, role] of Object.entries(roles)) {
71796
72914
  for (const perm of role.permissions) {
71797
72915
  if (!PERMISSION_SET.has(perm)) {
71798
72916
  errors.push({
@@ -71802,6 +72920,23 @@ function semanticValidate(yaml) {
71802
72920
  }
71803
72921
  }
71804
72922
  }
72923
+ for (const [slug, group] of Object.entries(groups)) {
72924
+ const seenMembers = /* @__PURE__ */ new Set();
72925
+ const members = group.members ?? [];
72926
+ for (let i = 0; i < members.length; i++) {
72927
+ const member = members[i];
72928
+ const key = declaredMemberKey(member);
72929
+ if (seenMembers.has(key)) {
72930
+ errors.push({
72931
+ path: `access.groups.${slug}.members[${i}]`,
72932
+ message: `Duplicate group member '${key}'`
72933
+ });
72934
+ continue;
72935
+ }
72936
+ seenMembers.add(key);
72937
+ }
72938
+ }
72939
+ const seenBindingKeys = /* @__PURE__ */ new Set();
71805
72940
  const bindings = yaml.access.bindings ?? [];
71806
72941
  for (let i = 0; i < bindings.length; i++) {
71807
72942
  const binding = bindings[i];
@@ -71813,18 +72948,73 @@ function semanticValidate(yaml) {
71813
72948
  });
71814
72949
  }
71815
72950
  }
71816
- if (binding.scope === "project" && !binding.project_id) {
72951
+ if (binding.project_id !== void 0 && binding.project_id.trim() === "") {
72952
+ errors.push({
72953
+ path: `access.bindings[${i}].project_id`,
72954
+ message: "project_id cannot be empty when provided"
72955
+ });
72956
+ }
72957
+ if (binding.subject.type === "group" && !groupSlugs.has(binding.subject.id)) {
72958
+ errors.push({
72959
+ path: `access.bindings[${i}].subject.id`,
72960
+ message: `Group '${binding.subject.id}' is not defined in access.groups`
72961
+ });
72962
+ }
72963
+ const normalizedScope = normalizeBindingScope(binding.scope);
72964
+ const requiredScopeTypes = /* @__PURE__ */ new Set();
72965
+ let needsWritableOrgFsScope = false;
72966
+ let needsWritableOrgDocsScope = false;
72967
+ for (const roleName of binding.roles) {
72968
+ const role = roles[roleName];
72969
+ if (!role) continue;
72970
+ for (const permission of role.permissions) {
72971
+ const scopeType = permissionScopeType(permission);
72972
+ if (scopeType) {
72973
+ requiredScopeTypes.add(scopeType);
72974
+ }
72975
+ if (permission === "orgfs:write" || permission === "orgfs:admin") {
72976
+ needsWritableOrgFsScope = true;
72977
+ }
72978
+ if (permission === "orgdocs:write" || permission === "orgdocs:admin") {
72979
+ needsWritableOrgDocsScope = true;
72980
+ }
72981
+ }
72982
+ }
72983
+ if (requiredScopeTypes.has("orgfs") && !hasPrefixScope(normalizedScope, "orgfs", needsWritableOrgFsScope)) {
72984
+ errors.push({
72985
+ path: `access.bindings[${i}].scope`,
72986
+ 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"
72987
+ });
72988
+ }
72989
+ if (requiredScopeTypes.has("orgdocs") && !hasPrefixScope(normalizedScope, "orgdocs", needsWritableOrgDocsScope)) {
71817
72990
  errors.push({
71818
- path: `access.bindings[${i}]`,
71819
- message: "Project-scoped binding requires a project_id"
72991
+ path: `access.bindings[${i}].scope`,
72992
+ 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
72993
  });
71821
72994
  }
71822
- if (binding.scope === "org" && binding.project_id) {
72995
+ if (requiredScopeTypes.has("envdb") && !hasEnvDbScope(normalizedScope)) {
71823
72996
  errors.push({
71824
- path: `access.bindings[${i}]`,
71825
- message: "Org-scoped binding should not have a project_id"
72997
+ path: `access.bindings[${i}].scope`,
72998
+ message: "Binding includes envdb permissions but scope.envdb.schemas/tables is missing"
71826
72999
  });
71827
73000
  }
73001
+ for (const roleName of binding.roles) {
73002
+ const dedupeKey = bindingMatchKey(
73003
+ roleName,
73004
+ binding.subject.type,
73005
+ binding.subject.id,
73006
+ binding.project_id ?? null,
73007
+ normalizedScope
73008
+ );
73009
+ if (seenBindingKeys.has(dedupeKey)) {
73010
+ errors.push({
73011
+ path: `access.bindings[${i}]`,
73012
+ message: `Duplicate binding tuple for role '${roleName}' and subject '${binding.subject.type}:${binding.subject.id}'`
73013
+ });
73014
+ } else {
73015
+ seenBindingKeys.add(dedupeKey);
73016
+ }
73017
+ }
71828
73018
  }
71829
73019
  return errors;
71830
73020
  }
@@ -71837,7 +73027,9 @@ async function handleValidate(flags, json) {
71837
73027
  valid: errors.length === 0,
71838
73028
  file: filePath,
71839
73029
  errors,
73030
+ groups: Object.keys(yaml.access.groups ?? {}).length,
71840
73031
  roles: Object.keys(yaml.access.roles ?? {}).length,
73032
+ members: Object.values(yaml.access.groups ?? {}).reduce((acc, group) => acc + (group.members?.length ?? 0), 0),
71841
73033
  bindings: (yaml.access.bindings ?? []).length
71842
73034
  }, json);
71843
73035
  return;
@@ -71850,28 +73042,124 @@ async function handleValidate(flags, json) {
71850
73042
  }
71851
73043
  process.exit(1);
71852
73044
  }
73045
+ const groupCount = Object.keys(yaml.access.groups ?? {}).length;
71853
73046
  const roleCount = Object.keys(yaml.access.roles ?? {}).length;
73047
+ const memberCount = Object.values(yaml.access.groups ?? {}).reduce((acc, group) => acc + (group.members?.length ?? 0), 0);
71854
73048
  const bindingCount = (yaml.access.bindings ?? []).length;
71855
- console.log(`Valid (${roleCount} roles, ${bindingCount} bindings)`);
73049
+ console.log(`Valid (${groupCount} groups, ${memberCount} members, ${roleCount} roles, ${bindingCount} bindings)`);
71856
73050
  }
71857
- function bindingKey(roleName, principalType, principalId, projectId) {
71858
- const scope = projectId ?? "org";
71859
- return `${roleName}|${principalType}|${principalId}|${scope}`;
73051
+ function resolveGroupPrincipalId(groupRef, groupsById, groupsBySlug) {
73052
+ if (groupsById.has(groupRef)) {
73053
+ return groupRef;
73054
+ }
73055
+ return groupsBySlug.get(groupRef)?.id ?? null;
71860
73056
  }
71861
73057
  async function computePlan(yaml, orgId, context2) {
71862
- const [apiRolesResponse, apiBindingsResponse] = await Promise.all([
73058
+ const [apiGroupsResponse, apiRolesResponse, apiBindingsResponse] = await Promise.all([
73059
+ requestJson(context2, `/orgs/${orgId}/access/groups`),
71863
73060
  requestJson(context2, `/orgs/${orgId}/access/roles`),
71864
73061
  requestJson(context2, `/orgs/${orgId}/access/bindings`)
71865
73062
  ]);
73063
+ const apiGroups = unwrapListResponse(apiGroupsResponse);
71866
73064
  const apiRoles = unwrapListResponse(apiRolesResponse);
71867
73065
  const apiBindings = unwrapListResponse(apiBindingsResponse);
73066
+ const groupMemberEntries = await Promise.all(
73067
+ apiGroups.map(async (group) => {
73068
+ const membersResponse = await requestJson(
73069
+ context2,
73070
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(group.id)}/members`
73071
+ );
73072
+ return [group.id, unwrapListResponse(membersResponse)];
73073
+ })
73074
+ );
73075
+ const membersByGroupId = new Map(
73076
+ groupMemberEntries.map(([groupId, members]) => [groupId, members])
73077
+ );
73078
+ const groupsBySlug = new Map(apiGroups.map((group) => [group.slug, group]));
73079
+ const groupsById = new Map(apiGroups.map((group) => [group.id, group]));
71868
73080
  const rolesByName = new Map(apiRoles.map((r) => [r.name, r]));
71869
73081
  const plan = {
73082
+ groups: [],
73083
+ group_members: [],
71870
73084
  roles: [],
71871
73085
  bindings: [],
73086
+ unchanged_groups: 0,
73087
+ unchanged_group_members: 0,
71872
73088
  unchanged_roles: 0,
71873
73089
  unchanged_bindings: 0
71874
73090
  };
73091
+ const declaredGroupSlugs = /* @__PURE__ */ new Set();
73092
+ for (const [slug, declaredGroup] of Object.entries(yaml.access.groups ?? {})) {
73093
+ declaredGroupSlugs.add(slug);
73094
+ const existing = groupsBySlug.get(slug);
73095
+ const desiredMembers = declaredGroup.members ?? [];
73096
+ if (!existing) {
73097
+ plan.groups.push({ action: "create", slug, group: declaredGroup });
73098
+ for (const member of desiredMembers) {
73099
+ plan.group_members.push({
73100
+ action: "add",
73101
+ groupSlug: slug,
73102
+ member
73103
+ });
73104
+ }
73105
+ continue;
73106
+ }
73107
+ const changes = [];
73108
+ const desiredName = declaredGroup.name ?? groupDefaultName(slug);
73109
+ const desiredDescription = declaredGroup.description ?? null;
73110
+ if (desiredName !== existing.name) {
73111
+ changes.push(`name: "${existing.name}" -> "${desiredName}"`);
73112
+ }
73113
+ if (desiredDescription !== (existing.description ?? null)) {
73114
+ changes.push(`description: "${existing.description ?? "(none)"}" -> "${desiredDescription ?? "(none)"}"`);
73115
+ }
73116
+ if (changes.length > 0) {
73117
+ plan.groups.push({
73118
+ action: "update",
73119
+ slug,
73120
+ id: existing.id,
73121
+ group: declaredGroup,
73122
+ changes
73123
+ });
73124
+ } else {
73125
+ plan.unchanged_groups++;
73126
+ }
73127
+ const existingMembers = membersByGroupId.get(existing.id) ?? [];
73128
+ const existingMemberKeys = new Set(existingMembers.map(apiMemberKey));
73129
+ const desiredMemberKeys = new Set(desiredMembers.map(declaredMemberKey));
73130
+ for (const member of desiredMembers) {
73131
+ const key = declaredMemberKey(member);
73132
+ if (existingMemberKeys.has(key)) {
73133
+ plan.unchanged_group_members++;
73134
+ } else {
73135
+ plan.group_members.push({
73136
+ action: "add",
73137
+ groupSlug: slug,
73138
+ member
73139
+ });
73140
+ }
73141
+ }
73142
+ for (const member of existingMembers) {
73143
+ const key = apiMemberKey(member);
73144
+ if (!desiredMemberKeys.has(key)) {
73145
+ plan.group_members.push({
73146
+ action: "remove",
73147
+ groupSlug: slug,
73148
+ groupId: existing.id,
73149
+ member
73150
+ });
73151
+ }
73152
+ }
73153
+ }
73154
+ for (const apiGroup of apiGroups) {
73155
+ if (!declaredGroupSlugs.has(apiGroup.slug)) {
73156
+ plan.groups.push({
73157
+ action: "prune",
73158
+ slug: apiGroup.slug,
73159
+ id: apiGroup.id
73160
+ });
73161
+ }
73162
+ }
71875
73163
  const declaredRoleNames = /* @__PURE__ */ new Set();
71876
73164
  for (const [name, declaredRole] of Object.entries(yaml.access.roles ?? {})) {
71877
73165
  declaredRoleNames.add(name);
@@ -71908,49 +73196,146 @@ async function computePlan(yaml, orgId, context2) {
71908
73196
  plan.roles.push({ action: "prune", name: apiRole.name, id: apiRole.id });
71909
73197
  }
71910
73198
  }
71911
- const existingBindingKeys = /* @__PURE__ */ new Map();
73199
+ const existingBindingByMatchKey = /* @__PURE__ */ new Map();
73200
+ const existingBindingByIdentityKey = /* @__PURE__ */ new Map();
71912
73201
  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);
73202
+ const identityKey = bindingIdentityKey(ab.role_name, ab.principal_type, ab.principal_id, ab.project_id);
73203
+ existingBindingByIdentityKey.set(identityKey, ab);
73204
+ const matchKey = bindingMatchKey(
73205
+ ab.role_name,
73206
+ ab.principal_type,
73207
+ ab.principal_id,
73208
+ ab.project_id,
73209
+ ab.scope_json
73210
+ );
73211
+ existingBindingByMatchKey.set(matchKey, ab);
71915
73212
  }
71916
- const matchedApiBindingKeys = /* @__PURE__ */ new Set();
73213
+ const matchedApiBindingIds = /* @__PURE__ */ new Set();
73214
+ const groupsMarkedForPrune = new Set(
73215
+ plan.groups.filter((group) => group.action === "prune").map((group) => group.id)
73216
+ );
71917
73217
  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(
73218
+ const normalizedDeclaredScope = normalizeBindingScope(declaredBinding.scope);
73219
+ const principalType = declaredBinding.subject.type;
73220
+ let principalIdHint = declaredBinding.subject.id;
73221
+ if (principalType === "group") {
73222
+ const resolvedGroupId = resolveGroupPrincipalId(declaredBinding.subject.id, groupsById, groupsBySlug);
73223
+ if (resolvedGroupId) {
73224
+ principalIdHint = resolvedGroupId;
73225
+ }
73226
+ }
73227
+ for (const roleName of [...new Set(declaredBinding.roles)]) {
73228
+ const identityKey = bindingIdentityKey(
71921
73229
  roleName,
71922
- declaredBinding.subject.type,
71923
- declaredBinding.subject.id,
71924
- projectId ?? null
73230
+ principalType,
73231
+ principalIdHint,
73232
+ declaredBinding.project_id ?? null
71925
73233
  );
71926
- if (existingBindingKeys.has(key)) {
71927
- matchedApiBindingKeys.add(key);
73234
+ const matchKey = bindingMatchKey(
73235
+ roleName,
73236
+ principalType,
73237
+ principalIdHint,
73238
+ declaredBinding.project_id ?? null,
73239
+ normalizedDeclaredScope
73240
+ );
73241
+ const exactMatch = existingBindingByMatchKey.get(matchKey);
73242
+ if (exactMatch) {
73243
+ matchedApiBindingIds.add(exactMatch.id);
71928
73244
  plan.unchanged_bindings++;
71929
- } else {
71930
- plan.bindings.push({ action: "create", binding: declaredBinding, roleName });
73245
+ continue;
71931
73246
  }
73247
+ const identityMatch = existingBindingByIdentityKey.get(identityKey);
73248
+ if (identityMatch) {
73249
+ matchedApiBindingIds.add(identityMatch.id);
73250
+ const changes = [];
73251
+ if (!scopeEquals(identityMatch.scope_json, normalizedDeclaredScope)) {
73252
+ changes.push(
73253
+ `scope: ${formatBindingScope(identityMatch.scope_json)} -> ${formatBindingScope(normalizedDeclaredScope)}`
73254
+ );
73255
+ }
73256
+ plan.bindings.push({
73257
+ action: "replace",
73258
+ existing: identityMatch,
73259
+ binding: declaredBinding,
73260
+ roleName,
73261
+ principalIdHint,
73262
+ changes
73263
+ });
73264
+ continue;
73265
+ }
73266
+ plan.bindings.push({
73267
+ action: "create",
73268
+ binding: declaredBinding,
73269
+ roleName,
73270
+ principalIdHint
73271
+ });
71932
73272
  }
71933
73273
  }
71934
- for (const [key, ab] of existingBindingKeys) {
71935
- if (!matchedApiBindingKeys.has(key)) {
71936
- plan.bindings.push({ action: "prune", binding: ab });
73274
+ for (const ab of apiBindings) {
73275
+ if (matchedApiBindingIds.has(ab.id)) {
73276
+ continue;
71937
73277
  }
73278
+ if (ab.principal_type === "group" && groupsMarkedForPrune.has(ab.principal_id)) {
73279
+ continue;
73280
+ }
73281
+ plan.bindings.push({ action: "prune", binding: ab });
71938
73282
  }
71939
73283
  return plan;
71940
73284
  }
71941
73285
  function hasChanges(plan, prune) {
73286
+ const groupChanges = plan.groups.filter(
73287
+ (g) => g.action === "create" || g.action === "update" || g.action === "prune" && prune
73288
+ );
73289
+ const memberChanges = plan.group_members.filter(
73290
+ (m) => m.action === "add" || m.action === "remove"
73291
+ );
71942
73292
  const roleChanges = plan.roles.filter(
71943
73293
  (r) => r.action === "create" || r.action === "update" || r.action === "prune" && prune
71944
73294
  );
71945
73295
  const bindingChanges = plan.bindings.filter(
71946
- (b) => b.action === "create" || b.action === "prune" && prune
73296
+ (b) => b.action === "create" || b.action === "replace" || b.action === "prune" && prune
71947
73297
  );
71948
- return roleChanges.length > 0 || bindingChanges.length > 0;
73298
+ return groupChanges.length > 0 || memberChanges.length > 0 || roleChanges.length > 0 || bindingChanges.length > 0;
71949
73299
  }
71950
73300
  function printPlan(plan, orgId, prune) {
71951
73301
  console.log(`
71952
73302
  Access Plan for ${orgId}:
71953
73303
  `);
73304
+ const groupCreates = plan.groups.filter((g) => g.action === "create");
73305
+ const groupUpdates = plan.groups.filter((g) => g.action === "update");
73306
+ const groupPrunes = plan.groups.filter((g) => g.action === "prune");
73307
+ if (groupCreates.length > 0 || groupUpdates.length > 0 || groupPrunes.length > 0) {
73308
+ console.log("Groups:");
73309
+ for (const group of groupCreates) {
73310
+ const name = group.group.name ?? groupDefaultName(group.slug);
73311
+ console.log(` + CREATE ${group.slug} (${name})`);
73312
+ }
73313
+ for (const group of groupUpdates) {
73314
+ console.log(` ~ UPDATE ${group.slug}: ${group.changes.join("; ")}`);
73315
+ }
73316
+ if (groupPrunes.length > 0) {
73317
+ if (prune) {
73318
+ for (const group of groupPrunes) {
73319
+ console.log(` - DELETE ${group.slug} (${group.id})`);
73320
+ }
73321
+ } else {
73322
+ console.log(` ? Undeclared (not pruned): ${groupPrunes.map((group) => group.slug).join(", ")}`);
73323
+ }
73324
+ }
73325
+ console.log("");
73326
+ }
73327
+ const memberAdds = plan.group_members.filter((m) => m.action === "add");
73328
+ const memberRemoves = plan.group_members.filter((m) => m.action === "remove");
73329
+ if (memberAdds.length > 0 || memberRemoves.length > 0) {
73330
+ console.log("Group Memberships:");
73331
+ for (const member of memberAdds) {
73332
+ console.log(` + ADD ${member.member.type}:${member.member.id} -> ${member.groupSlug}`);
73333
+ }
73334
+ for (const member of memberRemoves) {
73335
+ console.log(` - REMOVE ${member.member.principal_type}:${member.member.principal_id} -> ${member.groupSlug}`);
73336
+ }
73337
+ console.log("");
73338
+ }
71954
73339
  const roleCreates = plan.roles.filter((r) => r.action === "create");
71955
73340
  const roleUpdates = plan.roles.filter((r) => r.action === "update");
71956
73341
  const rolePrunes = plan.roles.filter((r) => r.action === "prune");
@@ -71974,12 +73359,21 @@ Access Plan for ${orgId}:
71974
73359
  console.log("");
71975
73360
  }
71976
73361
  const bindingCreates = plan.bindings.filter((b) => b.action === "create");
73362
+ const bindingReplaces = plan.bindings.filter((b) => b.action === "replace");
71977
73363
  const bindingPrunes = plan.bindings.filter((b) => b.action === "prune");
71978
- if (bindingCreates.length > 0 || bindingPrunes.length > 0) {
73364
+ if (bindingCreates.length > 0 || bindingReplaces.length > 0 || bindingPrunes.length > 0) {
71979
73365
  console.log("Bindings:");
71980
73366
  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})`);
73367
+ const scopeLabel = b.binding.project_id ? `project: ${b.binding.project_id}` : "org-wide";
73368
+ console.log(
73369
+ ` + BIND ${b.roleName} -> ${b.binding.subject.type}:${b.binding.subject.id} (${scopeLabel}, scope=${formatBindingScope(b.binding.scope)})`
73370
+ );
73371
+ }
73372
+ for (const b of bindingReplaces) {
73373
+ const scopeLabel = b.binding.project_id ? `project: ${b.binding.project_id}` : "org-wide";
73374
+ console.log(
73375
+ ` ~ REPLACE ${b.roleName} -> ${b.binding.subject.type}:${b.binding.subject.id} (${scopeLabel}): ${b.changes.join("; ")}`
73376
+ );
71983
73377
  }
71984
73378
  if (bindingPrunes.length > 0) {
71985
73379
  if (prune) {
@@ -71993,9 +73387,11 @@ Access Plan for ${orgId}:
71993
73387
  }
71994
73388
  console.log("");
71995
73389
  }
71996
- const totalUnchanged = plan.unchanged_roles + plan.unchanged_bindings;
73390
+ const totalUnchanged = plan.unchanged_groups + plan.unchanged_group_members + plan.unchanged_roles + plan.unchanged_bindings;
71997
73391
  if (totalUnchanged > 0) {
71998
- console.log(`Unchanged: ${plan.unchanged_roles} role(s), ${plan.unchanged_bindings} binding(s)`);
73392
+ console.log(
73393
+ `Unchanged: ${plan.unchanged_groups} group(s), ${plan.unchanged_group_members} member(s), ${plan.unchanged_roles} role(s), ${plan.unchanged_bindings} binding(s)`
73394
+ );
71999
73395
  }
72000
73396
  if (!hasChanges(plan, prune)) {
72001
73397
  console.log("No changes needed.");
@@ -72004,6 +73400,52 @@ Access Plan for ${orgId}:
72004
73400
  function planToJson(plan, orgId) {
72005
73401
  return {
72006
73402
  org_id: orgId,
73403
+ groups: {
73404
+ create: plan.groups.filter((group) => group.action === "create").map((group) => {
73405
+ const create = group;
73406
+ return {
73407
+ slug: create.slug,
73408
+ name: create.group.name ?? groupDefaultName(create.slug),
73409
+ description: create.group.description ?? null
73410
+ };
73411
+ }),
73412
+ update: plan.groups.filter((group) => group.action === "update").map((group) => {
73413
+ const update = group;
73414
+ return {
73415
+ slug: update.slug,
73416
+ id: update.id,
73417
+ changes: update.changes
73418
+ };
73419
+ }),
73420
+ prune: plan.groups.filter((group) => group.action === "prune").map((group) => {
73421
+ const prune = group;
73422
+ return {
73423
+ slug: prune.slug,
73424
+ id: prune.id
73425
+ };
73426
+ }),
73427
+ unchanged: plan.unchanged_groups
73428
+ },
73429
+ group_members: {
73430
+ add: plan.group_members.filter((member) => member.action === "add").map((member) => {
73431
+ const add = member;
73432
+ return {
73433
+ group_slug: add.groupSlug,
73434
+ principal_type: add.member.type,
73435
+ principal_id: add.member.id
73436
+ };
73437
+ }),
73438
+ remove: plan.group_members.filter((member) => member.action === "remove").map((member) => {
73439
+ const remove = member;
73440
+ return {
73441
+ group_slug: remove.groupSlug,
73442
+ group_id: remove.groupId,
73443
+ principal_type: remove.member.principal_type,
73444
+ principal_id: remove.member.principal_id
73445
+ };
73446
+ }),
73447
+ unchanged: plan.unchanged_group_members
73448
+ },
72007
73449
  roles: {
72008
73450
  create: plan.roles.filter((r) => r.action === "create").map((r) => {
72009
73451
  const cr = r;
@@ -72026,10 +73468,22 @@ function planToJson(plan, orgId) {
72026
73468
  role: cb.roleName,
72027
73469
  subject_type: cb.binding.subject.type,
72028
73470
  subject_id: cb.binding.subject.id,
72029
- scope: cb.binding.scope,
73471
+ scope: normalizeBindingScope(cb.binding.scope) ?? null,
72030
73472
  project_id: cb.binding.project_id
72031
73473
  };
72032
73474
  }),
73475
+ replace: plan.bindings.filter((b) => b.action === "replace").map((b) => {
73476
+ const rb = b;
73477
+ return {
73478
+ id: rb.existing.id,
73479
+ role: rb.roleName,
73480
+ subject_type: rb.binding.subject.type,
73481
+ subject_id: rb.binding.subject.id,
73482
+ scope: normalizeBindingScope(rb.binding.scope) ?? null,
73483
+ project_id: rb.binding.project_id,
73484
+ changes: rb.changes
73485
+ };
73486
+ }),
72033
73487
  prune: plan.bindings.filter((b) => b.action === "prune").map((b) => {
72034
73488
  const pb = b;
72035
73489
  return {
@@ -72064,6 +73518,26 @@ ${lines.join("\n")}`);
72064
73518
  }
72065
73519
  printPlan(plan, orgId, false);
72066
73520
  }
73521
+ async function fetchOrgGroups(orgId, context2) {
73522
+ const groupsResponse = await requestJson(
73523
+ context2,
73524
+ `/orgs/${orgId}/access/groups`
73525
+ );
73526
+ return unwrapListResponse(groupsResponse);
73527
+ }
73528
+ function resolveBindingPrincipalIdForApply(principalType, principalIdHint, groupIdsBySlug, groupIds) {
73529
+ if (principalType !== "group") {
73530
+ return principalIdHint;
73531
+ }
73532
+ if (groupIds.has(principalIdHint)) {
73533
+ return principalIdHint;
73534
+ }
73535
+ const fromSlug = groupIdsBySlug.get(principalIdHint);
73536
+ if (fromSlug) {
73537
+ return fromSlug;
73538
+ }
73539
+ throw new Error(`Group '${principalIdHint}' does not exist in the target org`);
73540
+ }
72067
73541
  async function handleSync(flags, context2, json) {
72068
73542
  const orgId = getStringFlag(flags, ["org"]) ?? context2.orgId;
72069
73543
  if (!orgId) {
@@ -72103,10 +73577,16 @@ ${lines.join("\n")}`);
72103
73577
  }
72104
73578
  }
72105
73579
  const applied = {
73580
+ groups_created: 0,
73581
+ groups_updated: 0,
73582
+ groups_deleted: 0,
73583
+ group_members_added: 0,
73584
+ group_members_removed: 0,
72106
73585
  roles_created: 0,
72107
73586
  roles_updated: 0,
72108
73587
  roles_deleted: 0,
72109
73588
  bindings_created: 0,
73589
+ bindings_replaced: 0,
72110
73590
  bindings_deleted: 0
72111
73591
  };
72112
73592
  for (const ra of plan.roles) {
@@ -72146,25 +73626,123 @@ ${lines.join("\n")}`);
72146
73626
  if (!json) console.log(` ~ Updated role '${ra.name}'`);
72147
73627
  }
72148
73628
  }
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
73629
+ for (const ga of plan.groups) {
73630
+ if (ga.action === "create") {
73631
+ const payload = {
73632
+ name: ga.group.name ?? groupDefaultName(ga.slug),
73633
+ slug: ga.slug,
73634
+ description: ga.group.description
73635
+ };
73636
+ await requestJson(
73637
+ context2,
73638
+ `/orgs/${orgId}/access/groups`,
73639
+ {
73640
+ method: "POST",
73641
+ body: payload
73642
+ }
73643
+ );
73644
+ applied.groups_created++;
73645
+ if (!json) console.log(` + Created group '${ga.slug}'`);
73646
+ continue;
73647
+ }
73648
+ if (ga.action === "update") {
73649
+ const payload = {
73650
+ name: ga.group.name ?? groupDefaultName(ga.slug),
73651
+ description: ga.group.description ?? null
72155
73652
  };
72156
- if (ba.binding.scope === "project" && ba.binding.project_id) {
72157
- body.project_id = ba.binding.project_id;
73653
+ await requestJson(
73654
+ context2,
73655
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(ga.id)}`,
73656
+ {
73657
+ method: "PATCH",
73658
+ body: payload
73659
+ }
73660
+ );
73661
+ applied.groups_updated++;
73662
+ if (!json) console.log(` ~ Updated group '${ga.slug}'`);
73663
+ }
73664
+ }
73665
+ const groupsAfterCreateUpdate = await fetchOrgGroups(orgId, context2);
73666
+ const groupIdsBySlug = new Map(groupsAfterCreateUpdate.map((group) => [group.slug, group.id]));
73667
+ const groupIds = new Set(groupsAfterCreateUpdate.map((group) => group.id));
73668
+ for (const ma of plan.group_members) {
73669
+ if (ma.action === "add") {
73670
+ const groupId2 = groupIdsBySlug.get(ma.groupSlug);
73671
+ if (!groupId2) {
73672
+ throw new Error(`Cannot add member: group '${ma.groupSlug}' does not exist in org '${orgId}'`);
72158
73673
  }
72159
73674
  await requestJson(
72160
73675
  context2,
72161
- `/orgs/${orgId}/access/bindings`,
72162
- { method: "POST", body }
73676
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(groupId2)}/members`,
73677
+ {
73678
+ method: "POST",
73679
+ body: {
73680
+ principal_type: ma.member.type,
73681
+ principal_id: ma.member.id
73682
+ }
73683
+ }
73684
+ );
73685
+ applied.group_members_added++;
73686
+ if (!json) console.log(` + Added ${ma.member.type}:${ma.member.id} to group '${ma.groupSlug}'`);
73687
+ continue;
73688
+ }
73689
+ const groupId = groupIdsBySlug.get(ma.groupSlug) ?? ma.groupId;
73690
+ await requestJson(
73691
+ context2,
73692
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(groupId)}/members/${ma.member.principal_type}/${encodeURIComponent(ma.member.principal_id)}`,
73693
+ { method: "DELETE" }
73694
+ );
73695
+ applied.group_members_removed++;
73696
+ if (!json) console.log(` - Removed ${ma.member.principal_type}:${ma.member.principal_id} from group '${ma.groupSlug}'`);
73697
+ }
73698
+ for (const ba of plan.bindings) {
73699
+ if (ba.action !== "create" && ba.action !== "replace") {
73700
+ continue;
73701
+ }
73702
+ const principalId = resolveBindingPrincipalIdForApply(
73703
+ ba.binding.subject.type,
73704
+ ba.principalIdHint,
73705
+ groupIdsBySlug,
73706
+ groupIds
73707
+ );
73708
+ if (ba.action === "replace") {
73709
+ await requestJson(
73710
+ context2,
73711
+ `/orgs/${orgId}/access/bindings/${ba.existing.id}`,
73712
+ { method: "DELETE" }
72163
73713
  );
73714
+ }
73715
+ const body = {
73716
+ role_name: ba.roleName,
73717
+ principal_type: ba.binding.subject.type,
73718
+ principal_id: principalId
73719
+ };
73720
+ if (ba.binding.project_id) {
73721
+ body.project_id = ba.binding.project_id;
73722
+ }
73723
+ const normalizedScope = normalizeBindingScope(ba.binding.scope);
73724
+ if (normalizedScope) {
73725
+ body.scope_json = normalizedScope;
73726
+ }
73727
+ await requestJson(
73728
+ context2,
73729
+ `/orgs/${orgId}/access/bindings`,
73730
+ { method: "POST", body }
73731
+ );
73732
+ const scopeLabel = ba.binding.project_id ? `project: ${ba.binding.project_id}` : "org-wide";
73733
+ if (ba.action === "create") {
72164
73734
  applied.bindings_created++;
72165
73735
  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})`);
73736
+ console.log(
73737
+ ` + Bound ${ba.roleName} -> ${ba.binding.subject.type}:${principalId} (${scopeLabel}, scope=${formatBindingScope(ba.binding.scope)})`
73738
+ );
73739
+ }
73740
+ } else {
73741
+ applied.bindings_replaced++;
73742
+ if (!json) {
73743
+ console.log(
73744
+ ` ~ Rebound ${ba.roleName} -> ${ba.binding.subject.type}:${principalId} (${scopeLabel}, scope=${formatBindingScope(ba.binding.scope)})`
73745
+ );
72168
73746
  }
72169
73747
  }
72170
73748
  }
@@ -72183,6 +73761,17 @@ ${lines.join("\n")}`);
72183
73761
  }
72184
73762
  }
72185
73763
  }
73764
+ for (const ga of plan.groups) {
73765
+ if (ga.action === "prune") {
73766
+ await requestJson(
73767
+ context2,
73768
+ `/orgs/${orgId}/access/groups/${encodeURIComponent(ga.id)}`,
73769
+ { method: "DELETE" }
73770
+ );
73771
+ applied.groups_deleted++;
73772
+ if (!json) console.log(` - Deleted group '${ga.slug}'`);
73773
+ }
73774
+ }
72186
73775
  for (const ra of plan.roles) {
72187
73776
  if (ra.action === "prune") {
72188
73777
  await requestJson(
@@ -72200,18 +73789,24 @@ ${lines.join("\n")}`);
72200
73789
  return;
72201
73790
  }
72202
73791
  const parts = [];
73792
+ if (applied.groups_created > 0) parts.push(`${applied.groups_created} group(s) created`);
73793
+ if (applied.groups_updated > 0) parts.push(`${applied.groups_updated} group(s) updated`);
73794
+ if (applied.groups_deleted > 0) parts.push(`${applied.groups_deleted} group(s) deleted`);
73795
+ if (applied.group_members_added > 0) parts.push(`${applied.group_members_added} group member(s) added`);
73796
+ if (applied.group_members_removed > 0) parts.push(`${applied.group_members_removed} group member(s) removed`);
72203
73797
  if (applied.roles_created > 0) parts.push(`${applied.roles_created} role(s) created`);
72204
73798
  if (applied.roles_updated > 0) parts.push(`${applied.roles_updated} role(s) updated`);
72205
73799
  if (applied.roles_deleted > 0) parts.push(`${applied.roles_deleted} role(s) deleted`);
72206
73800
  if (applied.bindings_created > 0) parts.push(`${applied.bindings_created} binding(s) created`);
73801
+ if (applied.bindings_replaced > 0) parts.push(`${applied.bindings_replaced} binding(s) replaced`);
72207
73802
  if (applied.bindings_deleted > 0) parts.push(`${applied.bindings_deleted} binding(s) deleted`);
72208
73803
  console.log(`
72209
73804
  Sync complete: ${parts.join(", ")}`);
72210
73805
  }
72211
73806
 
72212
73807
  // src/commands/docs.ts
72213
- var import_node_fs15 = require("node:fs");
72214
- var import_node_path15 = require("node:path");
73808
+ var import_node_fs16 = require("node:fs");
73809
+ var import_node_path16 = require("node:path");
72215
73810
  function encodeDocPathParam(path6) {
72216
73811
  const trimmed = path6.startsWith("/") ? path6.slice(1) : path6;
72217
73812
  return encodeURIComponent(trimmed);
@@ -72270,9 +73865,9 @@ async function handleDocs(subcommand, positionals, flags, context2) {
72270
73865
  const filePath = getStringFlag(flags, ["file"]);
72271
73866
  const useStdin = flags.stdin === true || flags.stdin === "true";
72272
73867
  if (filePath) {
72273
- content = (0, import_node_fs15.readFileSync)((0, import_node_path15.resolve)(filePath), "utf-8");
73868
+ content = (0, import_node_fs16.readFileSync)((0, import_node_path16.resolve)(filePath), "utf-8");
72274
73869
  } else if (useStdin) {
72275
- content = (0, import_node_fs15.readFileSync)(0, "utf-8");
73870
+ content = (0, import_node_fs16.readFileSync)(0, "utf-8");
72276
73871
  } else {
72277
73872
  throw new Error("Provide --file <path> or --stdin to supply document content");
72278
73873
  }
@@ -72721,6 +74316,9 @@ function formatDuration2(seconds) {
72721
74316
  function pad(label, value, width = 12) {
72722
74317
  return ` ${label.padEnd(width)}${value}`;
72723
74318
  }
74319
+ function parseAvgDurationSeconds(stats) {
74320
+ return stats.avg_duration_seconds ?? stats.avg_duration_s ?? 0;
74321
+ }
72724
74322
  async function analyticsSummary(flags, context2, orgId) {
72725
74323
  const json = Boolean(flags.json);
72726
74324
  const window2 = getStringFlag(flags, ["window"]) ?? "7d";
@@ -72747,7 +74345,7 @@ async function analyticsSummary(flags, context2, orgId) {
72747
74345
  console.log("Pipelines");
72748
74346
  console.log(pad("Runs:", pipelines.runs));
72749
74347
  console.log(pad("Success Rate:", `${pipelines.success_rate.toFixed(1)}%`));
72750
- console.log(pad("Avg Duration:", formatDuration2(pipelines.avg_duration_seconds)));
74348
+ console.log(pad("Avg Duration:", formatDuration2(parseAvgDurationSeconds(pipelines))));
72751
74349
  console.log("");
72752
74350
  console.log("Environments");
72753
74351
  console.log(pad("Total:", environments.total));
@@ -72766,17 +74364,15 @@ async function analyticsJobs(flags, context2, orgId) {
72766
74364
  outputJson(data, true);
72767
74365
  return;
72768
74366
  }
72769
- console.log(`Job Analytics (${data.window || window2} window)`);
74367
+ console.log(`Job Analytics (${window2} window)`);
72770
74368
  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
- }
74369
+ console.log(pad("As of:", data.as_of));
74370
+ console.log("");
74371
+ console.log("Jobs");
74372
+ console.log(pad("Created:", data.created));
74373
+ console.log(pad("Completed:", data.completed));
74374
+ console.log(pad("Failed:", data.failed));
74375
+ console.log(pad("Active:", data.active));
72780
74376
  }
72781
74377
  async function analyticsPipelines(flags, context2, orgId) {
72782
74378
  const json = Boolean(flags.json);
@@ -72789,22 +74385,14 @@ async function analyticsPipelines(flags, context2, orgId) {
72789
74385
  outputJson(data, true);
72790
74386
  return;
72791
74387
  }
72792
- console.log(`Pipeline Analytics (${data.window || window2} window)`);
74388
+ console.log(`Pipeline Analytics (${window2} window)`);
72793
74389
  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
- }
74390
+ console.log(pad("As of:", data.as_of));
74391
+ console.log("");
74392
+ console.log("Pipelines");
74393
+ console.log(pad("Runs:", data.runs));
74394
+ console.log(pad("Success Rate:", `${data.success_rate.toFixed(1)}%`));
74395
+ console.log(pad("Avg Duration:", formatDuration2(parseAvgDurationSeconds(data))));
72808
74396
  }
72809
74397
  async function analyticsEnvHealth(flags, context2, orgId) {
72810
74398
  const json = Boolean(flags.json);
@@ -72818,15 +74406,12 @@ async function analyticsEnvHealth(flags, context2, orgId) {
72818
74406
  }
72819
74407
  console.log("Environment Health");
72820
74408
  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
- }
74409
+ console.log("");
74410
+ console.log(pad("As of:", data.as_of));
74411
+ console.log(pad("Total:", data.total));
74412
+ console.log(pad("Healthy:", data.healthy));
74413
+ console.log(pad("Degraded:", data.degraded));
74414
+ console.log(pad("Unknown:", data.unknown));
72830
74415
  }
72831
74416
  async function handleAnalytics(subcommand, _positionals, flags, context2) {
72832
74417
  const orgId = getStringFlag(flags, ["org", "org-id", "org_id"]) ?? context2.orgId;
@@ -72844,7 +74429,7 @@ async function handleAnalytics(subcommand, _positionals, flags, context2) {
72844
74429
  return analyticsEnvHealth(flags, context2, orgId);
72845
74430
  default:
72846
74431
  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"
74432
+ "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
74433
  );
72849
74434
  }
72850
74435
  }
@@ -73217,6 +74802,284 @@ async function handleOllama(subcommand, positionals, flags, context2) {
73217
74802
  }
73218
74803
  }
73219
74804
 
74805
+ // src/commands/fs.ts
74806
+ var import_promises = require("node:fs/promises");
74807
+ var import_node_fs17 = require("node:fs");
74808
+ var import_node_os5 = require("node:os");
74809
+ function getOrgOrThrow(flags, context2) {
74810
+ const orgId = getStringFlag(flags, ["org", "org-id", "org_id"]) ?? context2.orgId;
74811
+ if (!orgId) {
74812
+ throw new Error("Missing org id. Provide --org or set a profile default.");
74813
+ }
74814
+ return orgId;
74815
+ }
74816
+ function parseModeFlag(value) {
74817
+ const raw = (value ?? "two-way").trim().toLowerCase();
74818
+ if (raw === "two-way" || raw === "two_way") return "two_way";
74819
+ if (raw === "push-only" || raw === "push_only") return "push_only";
74820
+ if (raw === "pull-only" || raw === "pull_only") return "pull_only";
74821
+ throw new Error(`Invalid mode: ${value}. Use two-way, push-only, or pull-only.`);
74822
+ }
74823
+ function parseStrategyFlag(value) {
74824
+ const raw = (value ?? "").trim().toLowerCase();
74825
+ if (raw === "pick-local" || raw === "pick_local") return "pick_local";
74826
+ if (raw === "pick-remote" || raw === "pick_remote") return "pick_remote";
74827
+ if (raw === "manual") return "manual";
74828
+ throw new Error(`Invalid strategy: ${value}. Use pick-local, pick-remote, or manual.`);
74829
+ }
74830
+ function parseGlobList(value) {
74831
+ if (!value) return void 0;
74832
+ return value.split(",").map((item) => item.trim()).filter(Boolean);
74833
+ }
74834
+ async function resolveLinkId(context2, orgId, flags) {
74835
+ const explicit = getStringFlag(flags, ["link", "link-id", "link_id"]);
74836
+ const links = await requestJson(context2, `/orgs/${orgId}/fs/links`);
74837
+ if (explicit) {
74838
+ const found = links.data.find((item) => item.id === explicit);
74839
+ if (!found) {
74840
+ throw new Error(`Link not found: ${explicit}`);
74841
+ }
74842
+ return found.id;
74843
+ }
74844
+ if (links.data.length === 0) {
74845
+ throw new Error("No sync links found. Run: eve fs sync init --org <org> --local <path>");
74846
+ }
74847
+ return links.data[0].id;
74848
+ }
74849
+ async function streamFsEvents(context2, orgId, afterSeq) {
74850
+ const headers = {
74851
+ Accept: "text/event-stream"
74852
+ };
74853
+ if (context2.token) {
74854
+ headers.Authorization = `Bearer ${context2.token}`;
74855
+ }
74856
+ const response = await fetch(`${context2.apiUrl}/orgs/${orgId}/fs/events/stream?after_seq=${afterSeq}`, {
74857
+ method: "GET",
74858
+ headers
74859
+ });
74860
+ if (!response.ok || !response.body) {
74861
+ const body = await response.text();
74862
+ throw new Error(`HTTP ${response.status}: ${body}`);
74863
+ }
74864
+ const reader = response.body.getReader();
74865
+ const decoder = new TextDecoder();
74866
+ let buffer = "";
74867
+ let currentEvent = "";
74868
+ let currentData = "";
74869
+ while (true) {
74870
+ const { done, value } = await reader.read();
74871
+ if (done) break;
74872
+ buffer += decoder.decode(value, { stream: true });
74873
+ const events = buffer.split("\n\n");
74874
+ buffer = events.pop() ?? "";
74875
+ for (const eventBlock of events) {
74876
+ const lines = eventBlock.split("\n");
74877
+ for (const line of lines) {
74878
+ if (line.startsWith("event:")) {
74879
+ currentEvent = line.slice(6).trim();
74880
+ } else if (line.startsWith("data:")) {
74881
+ currentData += `${line.slice(5).trim()}
74882
+ `;
74883
+ }
74884
+ }
74885
+ if (currentData) {
74886
+ const payload = currentData.trim();
74887
+ if (currentEvent === "fs_event") {
74888
+ try {
74889
+ const parsed = JSON.parse(payload);
74890
+ console.log(`[${parsed.seq}] ${parsed.event_type} ${parsed.path} (${parsed.source_side}) ${parsed.created_at}`);
74891
+ } catch {
74892
+ console.log(payload);
74893
+ }
74894
+ } else if (currentEvent === "error") {
74895
+ console.error(payload);
74896
+ }
74897
+ }
74898
+ currentEvent = "";
74899
+ currentData = "";
74900
+ }
74901
+ }
74902
+ }
74903
+ async function handleSync2(action, positionals, flags, context2) {
74904
+ const json = Boolean(flags.json);
74905
+ const orgId = getOrgOrThrow(flags, context2);
74906
+ switch (action) {
74907
+ case "init": {
74908
+ const localPath = getStringFlag(flags, ["local", "path"]);
74909
+ if (!localPath) {
74910
+ throw new Error("Usage: eve fs sync init --org <org_id> --local <path> [--mode two-way]");
74911
+ }
74912
+ const mode = parseModeFlag(getStringFlag(flags, ["mode"]));
74913
+ const includes = parseGlobList(getStringFlag(flags, ["include", "includes"]));
74914
+ const excludes = parseGlobList(getStringFlag(flags, ["exclude", "excludes"]));
74915
+ const publicKey = getStringFlag(flags, ["public-key", "public_key"]);
74916
+ const enroll = await requestJson(context2, `/orgs/${orgId}/fs/devices/enroll`, {
74917
+ method: "POST",
74918
+ body: {
74919
+ device_name: getStringFlag(flags, ["device-name", "device_name"]) ?? (0, import_node_os5.hostname)(),
74920
+ platform: process.platform,
74921
+ client_version: "dev",
74922
+ ...publicKey ? { public_key: publicKey } : {}
74923
+ }
74924
+ });
74925
+ const link = await requestJson(context2, `/orgs/${orgId}/fs/links`, {
74926
+ method: "POST",
74927
+ body: {
74928
+ device_id: enroll.device.id,
74929
+ mode,
74930
+ local_path: localPath,
74931
+ remote_path: getStringFlag(flags, ["remote-path", "remote_path"]) ?? "/",
74932
+ ...includes ? { includes } : {},
74933
+ ...excludes ? { excludes } : {}
74934
+ }
74935
+ });
74936
+ outputJson({
74937
+ device: enroll.device,
74938
+ enrollment: enroll.enrollment,
74939
+ link: link.link,
74940
+ runtime: link.runtime
74941
+ }, json, `Sync initialized for ${orgId}`);
74942
+ return;
74943
+ }
74944
+ case "status": {
74945
+ const [status, links] = await Promise.all([
74946
+ requestJson(context2, `/orgs/${orgId}/fs/status`),
74947
+ requestJson(context2, `/orgs/${orgId}/fs/links`)
74948
+ ]);
74949
+ if (json) {
74950
+ outputJson({ ...status, links_detail: links.data }, true);
74951
+ return;
74952
+ }
74953
+ console.log(`Org: ${orgId}`);
74954
+ console.log(`Gateway: ${status.gateway.status}`);
74955
+ if (status.gateway.last_heartbeat_at) console.log(`Last heartbeat: ${status.gateway.last_heartbeat_at}`);
74956
+ console.log(`Links: active=${status.links.active} paused=${status.links.paused} revoked=${status.links.revoked}`);
74957
+ console.log(`Latest seq: ${status.events.latest_seq}`);
74958
+ if (links.data.length > 0) {
74959
+ console.log("");
74960
+ for (const link of links.data) {
74961
+ console.log(`${link.id} ${link.mode} ${link.status} cursor=${link.last_cursor} lag_ms=${link.lag_ms ?? "n/a"} backlog=${link.backlog ?? 0}`);
74962
+ }
74963
+ }
74964
+ return;
74965
+ }
74966
+ case "logs": {
74967
+ const afterSeq = Number(getStringFlag(flags, ["after", "after-seq", "after_seq"]) ?? "0");
74968
+ const limit = Number(getStringFlag(flags, ["limit"]) ?? "200");
74969
+ const follow = flags.follow === true || flags.follow === "true";
74970
+ if (follow) {
74971
+ await streamFsEvents(context2, orgId, Number.isFinite(afterSeq) ? afterSeq : 0);
74972
+ return;
74973
+ }
74974
+ const events = await requestJson(
74975
+ context2,
74976
+ `/orgs/${orgId}/fs/events?after_seq=${Number.isFinite(afterSeq) ? afterSeq : 0}&limit=${Number.isFinite(limit) ? limit : 200}`
74977
+ );
74978
+ outputJson(events, json);
74979
+ return;
74980
+ }
74981
+ case "pause":
74982
+ case "resume":
74983
+ case "disconnect": {
74984
+ const linkId = await resolveLinkId(context2, orgId, flags);
74985
+ const nextStatus = action === "pause" ? "paused" : action === "resume" ? "active" : "revoked";
74986
+ const updated = await requestJson(context2, `/orgs/${orgId}/fs/links/${linkId}`, {
74987
+ method: "PATCH",
74988
+ body: { status: nextStatus }
74989
+ });
74990
+ outputJson(updated, json, `Link ${linkId} ${nextStatus}`);
74991
+ return;
74992
+ }
74993
+ case "mode": {
74994
+ const modeValue = getStringFlag(flags, ["set", "mode"]);
74995
+ if (!modeValue) {
74996
+ throw new Error("Usage: eve fs sync mode --org <org_id> --set <two-way|push-only|pull-only>");
74997
+ }
74998
+ const linkId = await resolveLinkId(context2, orgId, flags);
74999
+ const updated = await requestJson(context2, `/orgs/${orgId}/fs/links/${linkId}`, {
75000
+ method: "PATCH",
75001
+ body: { mode: parseModeFlag(modeValue) }
75002
+ });
75003
+ outputJson(updated, json, `Link ${linkId} mode updated`);
75004
+ return;
75005
+ }
75006
+ case "conflicts": {
75007
+ const openOnly = flags["open-only"] === true || flags["open_only"] === true || flags["open-only"] === "true" || flags["open_only"] === "true";
75008
+ const result = await requestJson(
75009
+ context2,
75010
+ `/orgs/${orgId}/fs/conflicts${openOnly ? "?open_only=true" : ""}`
75011
+ );
75012
+ outputJson(result, json);
75013
+ return;
75014
+ }
75015
+ case "resolve": {
75016
+ const conflictId = getStringFlag(flags, ["conflict", "conflict-id", "conflict_id"]) ?? positionals[0];
75017
+ if (!conflictId) {
75018
+ throw new Error("Usage: eve fs sync resolve --org <org_id> --conflict <conflict_id> --strategy <pick-remote|pick-local|manual>");
75019
+ }
75020
+ const strategy = parseStrategyFlag(getStringFlag(flags, ["strategy"]));
75021
+ const mergedContent = getStringFlag(flags, ["merged-content", "merged_content"]);
75022
+ const result = await requestJson(context2, `/orgs/${orgId}/fs/conflicts/${conflictId}/resolve`, {
75023
+ method: "POST",
75024
+ body: {
75025
+ strategy,
75026
+ ...mergedContent ? { merged_content: mergedContent } : {}
75027
+ }
75028
+ });
75029
+ outputJson(result, json, `Conflict ${conflictId} resolved`);
75030
+ return;
75031
+ }
75032
+ case "doctor": {
75033
+ const status = await requestJson(context2, `/orgs/${orgId}/fs/status`);
75034
+ const links = await requestJson(context2, `/orgs/${orgId}/fs/links`);
75035
+ const health = {
75036
+ auth: "ok",
75037
+ gateway_status: status.gateway.status,
75038
+ links: links.data.length,
75039
+ active_links: links.data.filter((item) => item.status === "active").length,
75040
+ cursor_drift: 0,
75041
+ local_path_checks: []
75042
+ };
75043
+ if (links.data.length > 0) {
75044
+ const minCursor = links.data.reduce((min, item) => Math.min(min, item.last_cursor), Number.POSITIVE_INFINITY);
75045
+ const cursorBase = Number.isFinite(minCursor) ? minCursor : 0;
75046
+ health.cursor_drift = Math.max(0, status.events.latest_seq - cursorBase);
75047
+ }
75048
+ for (const link of links.data) {
75049
+ let writable = false;
75050
+ try {
75051
+ await (0, import_promises.access)(link.local_path, import_node_fs17.constants.R_OK | import_node_fs17.constants.W_OK);
75052
+ writable = true;
75053
+ } catch {
75054
+ writable = false;
75055
+ }
75056
+ health.local_path_checks.push({
75057
+ link_id: link.id,
75058
+ local_path: link.local_path,
75059
+ writable
75060
+ });
75061
+ }
75062
+ outputJson(health, json, `FS sync doctor completed for ${orgId}`);
75063
+ return;
75064
+ }
75065
+ default:
75066
+ throw new Error(
75067
+ "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>"
75068
+ );
75069
+ }
75070
+ }
75071
+ async function handleFs(subcommand, positionals, flags, context2) {
75072
+ switch (subcommand) {
75073
+ case "sync": {
75074
+ const action = positionals[0];
75075
+ await handleSync2(action, positionals.slice(1), flags, context2);
75076
+ return;
75077
+ }
75078
+ default:
75079
+ throw new Error("Usage: eve fs sync <init|status|logs|pause|resume|disconnect|mode|conflicts|resolve|doctor>");
75080
+ }
75081
+ }
75082
+
73220
75083
  // src/index.ts
73221
75084
  async function main() {
73222
75085
  const { flags, positionals } = parseArgs(process.argv.slice(2));
@@ -73343,6 +75206,9 @@ async function main() {
73343
75206
  case "ollama":
73344
75207
  await handleOllama(subcommand, rest, flags, context2);
73345
75208
  return;
75209
+ case "fs":
75210
+ await handleFs(subcommand, rest, flags, context2);
75211
+ return;
73346
75212
  default:
73347
75213
  showMainHelp();
73348
75214
  }