@better-update/cli 0.27.0 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -34,7 +34,7 @@ var __require = /* @__PURE__ */ createRequire(import.meta.url);
34
34
 
35
35
  //#endregion
36
36
  //#region package.json
37
- var version = "0.27.0";
37
+ var version = "0.28.0";
38
38
 
39
39
  //#endregion
40
40
  //#region src/lib/interactive-mode.ts
@@ -527,6 +527,49 @@ var AndroidUploadKeystoresGroup = class extends HttpApiGroup.make("androidUpload
527
527
  description: "Manage Android signing keystores"
528
528
  })) {};
529
529
 
530
+ //#endregion
531
+ //#region ../../packages/api/src/domain/api-key.ts
532
+ var ApiKey = class extends Schema.Class("ApiKey")({
533
+ id: Id,
534
+ name: Schema.NullOr(Schema.String),
535
+ start: Schema.NullOr(Schema.String),
536
+ prefix: Schema.NullOr(Schema.String),
537
+ enabled: Schema.Boolean,
538
+ createdAt: DateTimeString,
539
+ expiresAt: Schema.NullOr(DateTimeString)
540
+ }) {};
541
+ var CreatedApiKey = class extends Schema.Class("CreatedApiKey")({
542
+ id: Id,
543
+ name: Schema.NullOr(Schema.String),
544
+ start: Schema.NullOr(Schema.String),
545
+ prefix: Schema.NullOr(Schema.String),
546
+ enabled: Schema.Boolean,
547
+ createdAt: DateTimeString,
548
+ expiresAt: Schema.NullOr(DateTimeString),
549
+ key: Schema.String
550
+ }) {};
551
+ const CreateApiKeyBody = Schema.Struct({
552
+ name: Name120,
553
+ expiresInDays: Schema.optional(Schema.Number.pipe(Schema.int(), Schema.positive()))
554
+ });
555
+ const ApiKeyList = Schema.Struct({ items: Schema.Array(ApiKey) });
556
+
557
+ //#endregion
558
+ //#region ../../packages/api/src/groups/api-keys.ts
559
+ var ApiKeysGroup = class extends HttpApiGroup.make("api-keys").add(HttpApiEndpoint.get("list", "/api/api-keys").addSuccess(ApiKeyList).annotateContext(OpenApi.annotations({
560
+ title: "List API keys",
561
+ description: "List the active organization's API keys (hashed secret never exposed; only the `start` prefix for identification)"
562
+ }))).add(HttpApiEndpoint.post("create", "/api/api-keys").setPayload(CreateApiKeyBody).addSuccess(CreatedApiKey, { status: 201 }).annotateContext(OpenApi.annotations({
563
+ title: "Create API key",
564
+ description: "Mint a new API key for the active organization. The plaintext key is returned ONCE; only its hash is stored"
565
+ }))).add(HttpApiEndpoint.del("revoke")`/api/api-keys/${idParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
566
+ title: "Revoke API key",
567
+ description: "Delete an API key by id (org-scoped; no cross-organization deletes)"
568
+ }))).addError(NotFound).addError(Forbidden).annotateContext(OpenApi.annotations({
569
+ title: "API Keys",
570
+ description: "IAM-gated organization API key mint / list / revoke"
571
+ })) {};
572
+
530
573
  //#endregion
531
574
  //#region ../../packages/api/src/domain/apple-team.ts
532
575
  const AppleTeamType = Schema.Literal("IN_HOUSE", "COMPANY_ORGANIZATION", "INDIVIDUAL");
@@ -861,7 +904,7 @@ var AssetsGroup = class extends HttpApiGroup.make("assets").add(HttpApiEndpoint.
861
904
 
862
905
  //#endregion
863
906
  //#region ../../packages/api/src/domain/audit-log.ts
864
- const AuditLogResourceType = Schema.Literal("project", "branch", "channel", "update", "build", "appleCredential", "androidCredential", "iosBundleConfiguration", "envVar", "device", "webhook", "iosAppMetadata", "submission", "vaultAccess");
907
+ const AuditLogResourceType = Schema.Literal("project", "branch", "channel", "update", "build", "appleCredential", "androidCredential", "iosBundleConfiguration", "envVar", "device", "webhook", "iosAppMetadata", "submission", "vaultAccess", "policy", "group", "policyAttachment", "apiKey", "invitation", "member", "organization");
865
908
  const AuditLogSource = Schema.Literal("session", "api-key");
866
909
  var AuditLog = class extends Schema.Class("AuditLog")({
867
910
  id: Id,
@@ -1178,44 +1221,6 @@ var BuildsGroup = class extends HttpApiGroup.make("builds").add(HttpApiEndpoint.
1178
1221
  description: "Build artifact upload, tracking, and download endpoints"
1179
1222
  })) {};
1180
1223
 
1181
- //#endregion
1182
- //#region ../../packages/api/src/domain/channel-grant.ts
1183
- const GrantEffectSchema = Schema.Literal("allow", "deny");
1184
- /** One member's allow/deny set on a channel; `actions` are "resource:action". */
1185
- var ChannelGrant = class extends Schema.Class("ChannelGrant")({
1186
- id: Id,
1187
- memberId: Id,
1188
- scopeKind: Schema.Literal("channel"),
1189
- scopeId: Id,
1190
- effect: GrantEffectSchema,
1191
- actions: Schema.Array(Schema.String),
1192
- createdAt: DateTimeString
1193
- }) {};
1194
- /** Upsert one (member, channel, effect) grant. effect defaults to "allow". */
1195
- const UpsertChannelGrantBody = Schema.Struct({
1196
- effect: Schema.optionalWith(GrantEffectSchema, { default: () => "allow" }),
1197
- actions: Schema.Array(Schema.String).pipe(Schema.minItems(1))
1198
- });
1199
- const ListChannelGrantsParams = Schema.Struct({});
1200
- const DeleteChannelGrantResult = Schema.Struct({ deleted: Schema.Number });
1201
-
1202
- //#endregion
1203
- //#region ../../packages/api/src/groups/channel-grants.ts
1204
- const memberIdParam = HttpApiSchema.param("memberId", Schema.String);
1205
- var ChannelGrantsGroup = class extends HttpApiGroup.make("channelGrants").add(HttpApiEndpoint.get("list")`/api/channels/${idParam}/grants`.setUrlParams(ListChannelGrantsParams).addSuccess(Schema.Array(ChannelGrant)).annotateContext(OpenApi.annotations({
1206
- title: "List channel grants",
1207
- description: "List all per-member allow/deny grants on a channel"
1208
- }))).add(HttpApiEndpoint.put("upsert")`/api/channels/${idParam}/grants/${memberIdParam}`.setPayload(UpsertChannelGrantBody).addSuccess(ChannelGrant).annotateContext(OpenApi.annotations({
1209
- title: "Upsert channel grant",
1210
- description: "Create or replace a member's allow/deny grant on a channel"
1211
- }))).add(HttpApiEndpoint.del("delete")`/api/channels/${idParam}/grants/${memberIdParam}`.addSuccess(DeleteChannelGrantResult).annotateContext(OpenApi.annotations({
1212
- title: "Delete channel grant",
1213
- description: "Revoke a member's grants on a channel"
1214
- }))).addError(NotFound).addError(Forbidden).annotateContext(OpenApi.annotations({
1215
- title: "Channel grants",
1216
- description: "Per-channel ABAC permission grants (allow/deny by member)"
1217
- })) {};
1218
-
1219
1224
  //#endregion
1220
1225
  //#region ../../packages/api/src/domain/channel.ts
1221
1226
  var Channel = class extends Schema.Class("Channel")({
@@ -1471,71 +1476,6 @@ const EnvVarRevision = Schema.Struct({
1471
1476
  const EnvVarRevisionsResult = Schema.Struct({ items: Schema.Array(EnvVarRevision) });
1472
1477
  const RollbackEnvVarBody = Schema.Struct({ toRevisionId: Id });
1473
1478
 
1474
- //#endregion
1475
- //#region ../../packages/api/src/domain/env-grant.ts
1476
- /**
1477
- * One member's allow/deny set on a (project × environment) env-var scope.
1478
- * `scopeKind` is fixed to "env_var_environment". `scopeId` is the encoded
1479
- * `<projectId|global>:<environment>` token (server-built). `actions` are
1480
- * "resource:action" tokens (here always envVar:*).
1481
- */
1482
- var EnvGrant = class extends Schema.Class("EnvGrant")({
1483
- id: Id,
1484
- memberId: Id,
1485
- scopeKind: Schema.Literal("env_var_environment"),
1486
- scopeId: Schema.String,
1487
- effect: GrantEffectSchema,
1488
- actions: Schema.Array(Schema.String),
1489
- createdAt: DateTimeString
1490
- }) {};
1491
- /** A flattened row for the list UI: one member × environment cell. */
1492
- var EnvGrantRow = class extends Schema.Class("EnvGrantRow")({
1493
- memberId: Id,
1494
- environment: EnvVarEnvironment,
1495
- effect: GrantEffectSchema,
1496
- actions: Schema.Array(Schema.String)
1497
- }) {};
1498
- /**
1499
- * URL params for listing grants on a project-or-global scope. `projectId` is the
1500
- * sentinel "global" or a real project id (the server resolves null vs the
1501
- * sentinel). Carried as a query param.
1502
- */
1503
- const ListEnvGrantsParams = Schema.Struct({ projectId: Schema.String });
1504
- /**
1505
- * Upsert one (member, project-or-global, environment) grant. `projectId` null =
1506
- * org-global vault. effect defaults to "allow". actions are envVar:* tokens.
1507
- */
1508
- const UpsertEnvGrantBody = Schema.Struct({
1509
- memberId: Id,
1510
- projectId: Schema.NullOr(Id),
1511
- environment: EnvVarEnvironment,
1512
- effect: Schema.optionalWith(GrantEffectSchema, { default: () => "allow" }),
1513
- actions: Schema.Array(Schema.String).pipe(Schema.minItems(1))
1514
- });
1515
- /** Delete both effects for (member, project-or-global, environment). */
1516
- const DeleteEnvGrantBody = Schema.Struct({
1517
- memberId: Id,
1518
- projectId: Schema.NullOr(Id),
1519
- environment: EnvVarEnvironment
1520
- });
1521
- const DeleteEnvGrantResult = Schema.Struct({ deleted: Schema.Number });
1522
-
1523
- //#endregion
1524
- //#region ../../packages/api/src/groups/env-grants.ts
1525
- var EnvGrantsGroup = class extends HttpApiGroup.make("envGrants").add(HttpApiEndpoint.get("list", "/api/env-grants").setUrlParams(ListEnvGrantsParams).addSuccess(Schema.Array(EnvGrantRow)).annotateContext(OpenApi.annotations({
1526
- title: "List env-var environment grants",
1527
- description: "List per-member allow/deny env-var grants on a project-or-global scope across all environments. projectId is a real id or the sentinel 'global'."
1528
- }))).add(HttpApiEndpoint.put("upsert", "/api/env-grants").setPayload(UpsertEnvGrantBody).addSuccess(EnvGrant).annotateContext(OpenApi.annotations({
1529
- title: "Upsert env-var environment grant",
1530
- description: "Create or replace a member's allow/deny env-var grant on one (project-or-global × environment) scope. projectId null = org-global."
1531
- }))).add(HttpApiEndpoint.del("delete", "/api/env-grants").setPayload(DeleteEnvGrantBody).addSuccess(DeleteEnvGrantResult).annotateContext(OpenApi.annotations({
1532
- title: "Delete env-var environment grants",
1533
- description: "Revoke both allow and deny grants for a member on one (project-or-global × environment) scope."
1534
- }))).addError(NotFound).addError(Forbidden).addError(BadRequest).annotateContext(OpenApi.annotations({
1535
- title: "Env-var environment grants",
1536
- description: "Per (project × environment) ABAC permission grants for env vars (allow/deny by member)"
1537
- })) {};
1538
-
1539
1479
  //#endregion
1540
1480
  //#region ../../packages/api/src/groups/env-vars.ts
1541
1481
  var EnvVarsGroup = class extends HttpApiGroup.make("env-vars").add(HttpApiEndpoint.post("create", "/api/env-vars").setPayload(CreateEnvVarBody).addSuccess(EnvVar, { status: 201 }).annotateContext(OpenApi.annotations({
@@ -1760,6 +1700,97 @@ var GoogleServiceAccountKeysGroup = class extends HttpApiGroup.make("googleServi
1760
1700
  description: "Manage Google Play + FCM service account JSON keys"
1761
1701
  })) {};
1762
1702
 
1703
+ //#endregion
1704
+ //#region ../../packages/api/src/domain/group.ts
1705
+ var Group = class extends Schema.Class("Group")({
1706
+ id: Id,
1707
+ organizationId: Id,
1708
+ name: Schema.NonEmptyString,
1709
+ description: Schema.NullOr(Schema.String),
1710
+ createdAt: DateTimeString,
1711
+ updatedAt: Schema.NullOr(DateTimeString)
1712
+ }) {};
1713
+ const CreateGroupBody = Schema.Struct({
1714
+ name: Schema.NonEmptyString,
1715
+ description: Schema.optional(Schema.String)
1716
+ });
1717
+ const UpdateGroupBody = Schema.Struct({
1718
+ name: Schema.optional(Schema.NonEmptyString),
1719
+ description: Schema.optional(Schema.NullOr(Schema.String))
1720
+ });
1721
+ var GroupMember = class extends Schema.Class("GroupMember")({
1722
+ memberId: Id,
1723
+ createdAt: DateTimeString
1724
+ }) {};
1725
+ const AddGroupMemberBody = Schema.Struct({ memberId: Id });
1726
+
1727
+ //#endregion
1728
+ //#region ../../packages/api/src/groups/groups.ts
1729
+ /** `:memberId` path parameter — the `member.id` of a group member. */
1730
+ const memberIdParam = HttpApiSchema.param("memberId", Id);
1731
+ var GroupsGroup = class extends HttpApiGroup.make("groups").add(HttpApiEndpoint.get("list", "/api/groups").addSuccess(Schema.Struct({ items: Schema.Array(Group) })).annotateContext(OpenApi.annotations({
1732
+ title: "List groups",
1733
+ description: "List member groups in the active organization"
1734
+ }))).add(HttpApiEndpoint.post("create", "/api/groups").setPayload(CreateGroupBody).addSuccess(Group, { status: 201 }).annotateContext(OpenApi.annotations({
1735
+ title: "Create group",
1736
+ description: "Create a member group for the active organization"
1737
+ }))).add(HttpApiEndpoint.get("get")`/api/groups/${idParam}`.addSuccess(Group).annotateContext(OpenApi.annotations({
1738
+ title: "Get group",
1739
+ description: "Fetch a single group by id"
1740
+ }))).add(HttpApiEndpoint.patch("update")`/api/groups/${idParam}`.setPayload(UpdateGroupBody).addSuccess(Group).annotateContext(OpenApi.annotations({
1741
+ title: "Update group",
1742
+ description: "Update a group's name or description"
1743
+ }))).add(HttpApiEndpoint.del("delete")`/api/groups/${idParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
1744
+ title: "Delete group",
1745
+ description: "Delete a group and sweep its memberships and policy attachments"
1746
+ }))).add(HttpApiEndpoint.get("listMembers")`/api/groups/${idParam}/members`.addSuccess(Schema.Struct({ items: Schema.Array(GroupMember) })).annotateContext(OpenApi.annotations({
1747
+ title: "List group members",
1748
+ description: "List the members belonging to a group"
1749
+ }))).add(HttpApiEndpoint.post("addMember")`/api/groups/${idParam}/members`.setPayload(AddGroupMemberBody).addSuccess(GroupMember, { status: 201 }).annotateContext(OpenApi.annotations({
1750
+ title: "Add group member",
1751
+ description: "Add an organization member to a group"
1752
+ }))).add(HttpApiEndpoint.del("removeMember")`/api/groups/${idParam}/members/${memberIdParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
1753
+ title: "Remove group member",
1754
+ description: "Remove a member from a group"
1755
+ }))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
1756
+ title: "Groups",
1757
+ description: "Member groups for collective policy attachment"
1758
+ })) {};
1759
+
1760
+ //#endregion
1761
+ //#region ../../packages/api/src/domain/invitation.ts
1762
+ var Invitation = class extends Schema.Class("Invitation")({
1763
+ id: Id,
1764
+ email: Schema.String,
1765
+ role: Schema.NullOr(Schema.String),
1766
+ status: Schema.String,
1767
+ expiresAt: DateTimeString,
1768
+ createdAt: DateTimeString
1769
+ }) {};
1770
+ const EMAIL_PATTERN = /^[^\s@]+@[^\s@]+\.[^\s@]+$/u;
1771
+ const InvitableRole = Schema.Literal("member");
1772
+ const CreateInvitationBody = Schema.Struct({
1773
+ email: Schema.String.pipe(Schema.minLength(1), Schema.maxLength(320), Schema.pattern(EMAIL_PATTERN)),
1774
+ role: Schema.optional(InvitableRole)
1775
+ });
1776
+ const InvitationList = Schema.Struct({ items: Schema.Array(Invitation) });
1777
+
1778
+ //#endregion
1779
+ //#region ../../packages/api/src/groups/invitations.ts
1780
+ var InvitationsGroup = class extends HttpApiGroup.make("invitations").add(HttpApiEndpoint.get("list", "/api/invitations").addSuccess(InvitationList).annotateContext(OpenApi.annotations({
1781
+ title: "List invitations",
1782
+ description: "List the active organization's invitations (all statuses, newest first)"
1783
+ }))).add(HttpApiEndpoint.post("create", "/api/invitations").setPayload(CreateInvitationBody).addSuccess(Invitation, { status: 201 }).annotateContext(OpenApi.annotations({
1784
+ title: "Create invitation",
1785
+ description: "Invite a member to the active organization. Writes a pending `invitation` row (better-auth's accept-invitation consumes it) and sends the invite email"
1786
+ }))).add(HttpApiEndpoint.del("cancel")`/api/invitations/${idParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
1787
+ title: "Cancel invitation",
1788
+ description: "Cancel a pending invitation by id (org-scoped). A canceled invitation can no longer be accepted"
1789
+ }))).addError(NotFound).addError(Forbidden).annotateContext(OpenApi.annotations({
1790
+ title: "Invitations",
1791
+ description: "IAM-gated organization invitation create / list / cancel"
1792
+ })) {};
1793
+
1763
1794
  //#endregion
1764
1795
  //#region ../../packages/api/src/domain/ios-app-metadata.ts
1765
1796
  const AscAppId = Schema.String.pipe(Schema.pattern(/^[0-9]{1,30}$/u, { message: () => "ASC App ID must be 1-30 digits" }));
@@ -1896,7 +1927,13 @@ const Me = Schema.Struct({
1896
1927
  /** Authentication source — "session" for browser + CLI sessions, "api-key" for API-key (CI) bearer tokens. */
1897
1928
  source: Schema.Literal("session", "api-key"),
1898
1929
  /** Email or descriptor identifying the actor — useful when `user` is null (api-key auth). */
1899
- actorEmail: Schema.String
1930
+ actorEmail: Schema.String,
1931
+ /** Holds `invitation:create` on `org` — gates the Invite button. */
1932
+ canInviteMembers: Schema.Boolean,
1933
+ /** Holds `member:delete` on `org` — gates the per-member Remove action. */
1934
+ canRemoveMembers: Schema.Boolean,
1935
+ /** Holds `policy:update` on `org` — gates the per-member Manage-policies action. */
1936
+ canManagePolicies: Schema.Boolean
1900
1937
  });
1901
1938
 
1902
1939
  //#endregion
@@ -1910,51 +1947,13 @@ var MeGroup = class extends HttpApiGroup.make("me").add(HttpApiEndpoint.get("get
1910
1947
  })) {};
1911
1948
 
1912
1949
  //#endregion
1913
- //#region ../../packages/api/src/domain/org-role.ts
1914
- /** One resource→actions grant inside a role's permission set. */
1915
- const PermissionGrantSchema = Schema.Struct({
1916
- resource: Schema.String,
1917
- actions: Schema.Array(Schema.String)
1918
- });
1919
- var OrgRole = class extends Schema.Class("OrgRole")({
1920
- id: Id,
1921
- organizationId: Id,
1922
- role: Schema.String,
1923
- permissions: Schema.Array(PermissionGrantSchema),
1924
- createdAt: DateTimeString,
1925
- updatedAt: Schema.NullOr(DateTimeString)
1926
- }) {};
1927
- const CreateOrgRoleBody = Schema.Struct({
1928
- name: Schema.String.pipe(Schema.minLength(1)),
1929
- permissions: Schema.Array(PermissionGrantSchema)
1930
- });
1931
- const UpdateOrgRoleBody = Schema.Struct({
1932
- permissions: Schema.optional(Schema.Array(PermissionGrantSchema)),
1933
- name: Schema.optional(Schema.String.pipe(Schema.minLength(1)))
1934
- });
1935
- const ListOrgRolesParams = Schema.Struct({ organizationId: Id });
1936
- const DeleteOrgRoleResult = Schema.Struct({ deleted: Schema.Number });
1937
-
1938
- //#endregion
1939
- //#region ../../packages/api/src/groups/org-roles.ts
1940
- var OrgRolesGroup = class extends HttpApiGroup.make("roles").add(HttpApiEndpoint.get("list", "/api/roles").setUrlParams(ListOrgRolesParams).addSuccess(Schema.Array(OrgRole)).annotateContext(OpenApi.annotations({
1941
- title: "List custom roles",
1942
- description: "List all custom roles defined for an organization"
1943
- }))).add(HttpApiEndpoint.post("create", "/api/roles").setPayload(CreateOrgRoleBody).addSuccess(OrgRole, { status: 201 }).annotateContext(OpenApi.annotations({
1944
- title: "Create custom role",
1945
- description: "Create a new custom role with a permission set"
1946
- }))).add(HttpApiEndpoint.get("get")`/api/roles/${idParam}`.addSuccess(OrgRole).annotateContext(OpenApi.annotations({
1947
- title: "Get custom role",
1948
- description: "Fetch a single custom role by id"
1949
- }))).add(HttpApiEndpoint.patch("update")`/api/roles/${idParam}`.setPayload(UpdateOrgRoleBody).addSuccess(OrgRole).annotateContext(OpenApi.annotations({
1950
- title: "Update custom role",
1951
- description: "Rename a custom role or replace its permission set"
1952
- }))).add(HttpApiEndpoint.del("delete")`/api/roles/${idParam}`.addSuccess(DeleteOrgRoleResult).annotateContext(OpenApi.annotations({
1953
- title: "Delete custom role",
1954
- description: "Delete a custom role"
1950
+ //#region ../../packages/api/src/groups/members.ts
1951
+ var MembersGroup = class extends HttpApiGroup.make("members").add(HttpApiEndpoint.del("remove")`/api/members/${idParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
1952
+ title: "Remove member",
1953
+ description: "Remove a member from the active organization by member id (org-scoped; no cross-organization removes). Rejects removing the last owner (409). Membership role is `owner | member`; admin/developer/viewer access comes from policy attachments, not the role"
1955
1954
  }))).addError(NotFound).addError(Conflict).addError(Forbidden).annotateContext(OpenApi.annotations({
1956
- title: "Roles",
1957
- description: "Custom organization role management (dynamic access control)"
1955
+ title: "Members",
1956
+ description: "IAM-gated organization member removal"
1958
1957
  })) {};
1959
1958
 
1960
1959
  //#endregion
@@ -1987,6 +1986,134 @@ var OrgVaultGroup = class extends HttpApiGroup.make("orgVault").add(HttpApiEndpo
1987
1986
  description: "Manage the organization's end-to-end encrypted vault key wraps"
1988
1987
  })) {};
1989
1988
 
1989
+ //#endregion
1990
+ //#region ../../packages/api/src/domain/organization.ts
1991
+ var Organization = class extends Schema.Class("Organization")({
1992
+ id: Id,
1993
+ name: Schema.String,
1994
+ slug: Schema.String
1995
+ }) {};
1996
+ const UpdateOrganizationBody = Schema.Struct({
1997
+ name: Schema.optional(Schema.String.pipe(Schema.minLength(1), Schema.maxLength(120))),
1998
+ slug: Schema.optional(Schema.String.pipe(Schema.minLength(1), Schema.maxLength(120)))
1999
+ });
2000
+
2001
+ //#endregion
2002
+ //#region ../../packages/api/src/groups/organization.ts
2003
+ var OrganizationGroup = class extends HttpApiGroup.make("organization").add(HttpApiEndpoint.patch("update", "/api/organization").setPayload(UpdateOrganizationBody).addSuccess(Organization).annotateContext(OpenApi.annotations({
2004
+ title: "Update organization",
2005
+ description: "Rename / re-slug the active organization (IAM-gated by organization:update)"
2006
+ }))).addError(NotFound).addError(Conflict).addError(Forbidden).annotateContext(OpenApi.annotations({
2007
+ title: "Organization",
2008
+ description: "IAM-gated active-organization settings"
2009
+ })) {};
2010
+
2011
+ //#endregion
2012
+ //#region ../../packages/api/src/domain/policy.ts
2013
+ /** Whether a statement grants or denies the matched actions. */
2014
+ const PolicyEffect = Schema.Literal("allow", "deny");
2015
+ const PolicyStatement = Schema.Struct({
2016
+ effect: PolicyEffect,
2017
+ actions: Schema.Array(Schema.String).pipe(Schema.minItems(1)),
2018
+ resources: Schema.Array(Schema.String).pipe(Schema.minItems(1))
2019
+ });
2020
+ const PolicyDocument = Schema.Struct({ statements: Schema.Array(PolicyStatement) });
2021
+ var Policy = class extends Schema.Class("Policy")({
2022
+ id: Id,
2023
+ organizationId: Id,
2024
+ name: Schema.NonEmptyString,
2025
+ description: Schema.NullOr(Schema.String),
2026
+ document: PolicyDocument,
2027
+ createdAt: DateTimeString,
2028
+ updatedAt: Schema.NullOr(DateTimeString)
2029
+ }) {};
2030
+ const CreatePolicyBody = Schema.Struct({
2031
+ name: Schema.NonEmptyString,
2032
+ description: Schema.optional(Schema.String),
2033
+ document: PolicyDocument
2034
+ });
2035
+ const UpdatePolicyBody = Schema.Struct({
2036
+ name: Schema.optional(Schema.NonEmptyString),
2037
+ description: Schema.optional(Schema.NullOr(Schema.String)),
2038
+ document: Schema.optional(PolicyDocument)
2039
+ });
2040
+
2041
+ //#endregion
2042
+ //#region ../../packages/api/src/groups/policies.ts
2043
+ var PoliciesGroup = class extends HttpApiGroup.make("policies").add(HttpApiEndpoint.get("list", "/api/policies").addSuccess(Schema.Struct({ items: Schema.Array(Policy) })).annotateContext(OpenApi.annotations({
2044
+ title: "List policies",
2045
+ description: "List policies in the active organization, merging the read-only managed presets (admin/developer/viewer) into the list"
2046
+ }))).add(HttpApiEndpoint.post("create", "/api/policies").setPayload(CreatePolicyBody).addSuccess(Policy, { status: 201 }).annotateContext(OpenApi.annotations({
2047
+ title: "Create policy",
2048
+ description: "Create a named IAM policy document for the active organization"
2049
+ }))).add(HttpApiEndpoint.get("get")`/api/policies/${idParam}`.addSuccess(Policy).annotateContext(OpenApi.annotations({
2050
+ title: "Get policy",
2051
+ description: "Fetch a single policy by id, resolving real ids or managed:* preset ids"
2052
+ }))).add(HttpApiEndpoint.patch("update")`/api/policies/${idParam}`.setPayload(UpdatePolicyBody).addSuccess(Policy).annotateContext(OpenApi.annotations({
2053
+ title: "Update policy",
2054
+ description: "Update a policy's name, description, or document; managed:* ids are rejected"
2055
+ }))).add(HttpApiEndpoint.del("delete")`/api/policies/${idParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
2056
+ title: "Delete policy",
2057
+ description: "Delete a policy and sweep its attachments; managed:* ids are rejected"
2058
+ }))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
2059
+ title: "Policies",
2060
+ description: "IAM policy documents (named, reusable permission grants)"
2061
+ })) {};
2062
+
2063
+ //#endregion
2064
+ //#region ../../packages/api/src/domain/policy-attachment.ts
2065
+ /** Whether an attachment binds a policy to a member, a group, or an api-key. */
2066
+ const PrincipalType = Schema.Literal("member", "group", "apikey");
2067
+ var PolicyAttachment = class extends Schema.Class("PolicyAttachment")({
2068
+ id: Id,
2069
+ organizationId: Id,
2070
+ policyId: Schema.String,
2071
+ principalType: PrincipalType,
2072
+ principalId: Id,
2073
+ createdAt: DateTimeString
2074
+ }) {};
2075
+ const AttachPolicyBody = Schema.Struct({ policyId: Schema.String });
2076
+
2077
+ //#endregion
2078
+ //#region ../../packages/api/src/groups/policy-attachments.ts
2079
+ /**
2080
+ * `:policyId` path parameter — a real `policy.id` or a managed preset id. Managed
2081
+ * ids contain a colon (`managed:admin`); the single path segment matches it as-is,
2082
+ * and clients URL-encode the colon when building the path.
2083
+ */
2084
+ const policyIdParam = HttpApiSchema.param("policyId", Schema.String);
2085
+ var PolicyAttachmentsGroup = class extends HttpApiGroup.make("policy-attachments").add(HttpApiEndpoint.get("listForMember")`/api/members/${idParam}/policies`.addSuccess(Schema.Struct({ items: Schema.Array(PolicyAttachment) })).annotateContext(OpenApi.annotations({
2086
+ title: "List member policy attachments",
2087
+ description: "List policies attached directly to an organization member"
2088
+ }))).add(HttpApiEndpoint.post("attachToMember")`/api/members/${idParam}/policies`.setPayload(AttachPolicyBody).addSuccess(PolicyAttachment, { status: 201 }).annotateContext(OpenApi.annotations({
2089
+ title: "Attach policy to member",
2090
+ description: "Attach a policy (real or managed) directly to a member"
2091
+ }))).add(HttpApiEndpoint.del("detachFromMember")`/api/members/${idParam}/policies/${policyIdParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
2092
+ title: "Detach policy from member",
2093
+ description: "Remove a policy attachment from a member"
2094
+ }))).add(HttpApiEndpoint.get("listForGroup")`/api/groups/${idParam}/policies`.addSuccess(Schema.Struct({ items: Schema.Array(PolicyAttachment) })).annotateContext(OpenApi.annotations({
2095
+ title: "List group policy attachments",
2096
+ description: "List policies attached to a group"
2097
+ }))).add(HttpApiEndpoint.post("attachToGroup")`/api/groups/${idParam}/policies`.setPayload(AttachPolicyBody).addSuccess(PolicyAttachment, { status: 201 }).annotateContext(OpenApi.annotations({
2098
+ title: "Attach policy to group",
2099
+ description: "Attach a policy (real or managed) to a group; members inherit it"
2100
+ }))).add(HttpApiEndpoint.del("detachFromGroup")`/api/groups/${idParam}/policies/${policyIdParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
2101
+ title: "Detach policy from group",
2102
+ description: "Remove a policy attachment from a group"
2103
+ }))).add(HttpApiEndpoint.get("listForApiKey")`/api/api-keys/${idParam}/policies`.addSuccess(Schema.Struct({ items: Schema.Array(PolicyAttachment) })).annotateContext(OpenApi.annotations({
2104
+ title: "List api-key policy attachments",
2105
+ description: "List policies attached to an api-key principal"
2106
+ }))).add(HttpApiEndpoint.post("attachToApiKey")`/api/api-keys/${idParam}/policies`.setPayload(AttachPolicyBody).addSuccess(PolicyAttachment, { status: 201 }).annotateContext(OpenApi.annotations({
2107
+ title: "Attach policy to api-key",
2108
+ description: "Attach a policy (real or managed) to an api-key principal"
2109
+ }))).add(HttpApiEndpoint.del("detachFromApiKey")`/api/api-keys/${idParam}/policies/${policyIdParam}`.addSuccess(DeletedResult).annotateContext(OpenApi.annotations({
2110
+ title: "Detach policy from api-key",
2111
+ description: "Remove a policy attachment from an api-key principal"
2112
+ }))).addError(NotFound).addError(Conflict).addError(BadRequest).addError(Forbidden).annotateContext(OpenApi.annotations({
2113
+ title: "Policy Attachments",
2114
+ description: "Bindings of policies to member, group, and api-key principals"
2115
+ })) {};
2116
+
1990
2117
  //#endregion
1991
2118
  //#region ../../packages/api/src/domain/project.ts
1992
2119
  var Project = class extends Schema.Class("Project")({
@@ -2307,7 +2434,7 @@ var WebhooksGroup = class extends HttpApiGroup.make("webhooks").add(HttpApiEndpo
2307
2434
 
2308
2435
  //#endregion
2309
2436
  //#region ../../packages/api/src/api.ts
2310
- var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(IosAppMetadataGroup).add(SubmissionsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(UserEncryptionKeysGroup).add(OrgVaultGroup).add(MeGroup).add(WebhooksGroup).add(AdminGroup).add(OrgRolesGroup).add(ChannelGrantsGroup).add(EnvGrantsGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
2437
+ var ManagementApi = class extends HttpApi.make("management-api").add(ProjectsGroup).add(BranchesGroup).add(ChannelsGroup).add(UpdatesGroup).add(AssetsGroup).add(AnalyticsGroup).add(BuildsGroup).add(EnvVarsGroup).add(FingerprintsGroup).add(AuditLogsGroup).add(DevicesGroup).add(AppleTeamsGroup).add(AppleDistributionCertificatesGroup).add(ApplePushKeysGroup).add(AscApiKeysGroup).add(AppleProvisioningProfilesGroup).add(GoogleServiceAccountKeysGroup).add(IosBundleConfigurationsGroup).add(IosAppMetadataGroup).add(SubmissionsGroup).add(AndroidApplicationIdentifiersGroup).add(AndroidUploadKeystoresGroup).add(AndroidBuildCredentialsGroup).add(BuildCredentialsGroup).add(UserEncryptionKeysGroup).add(OrgVaultGroup).add(MeGroup).add(WebhooksGroup).add(PoliciesGroup).add(GroupsGroup).add(PolicyAttachmentsGroup).add(ApiKeysGroup).add(InvitationsGroup).add(MembersGroup).add(OrganizationGroup).add(AdminGroup).middleware(Authentication).annotateContext(OpenApi.annotations({
2311
2438
  title: "Better Update Management API",
2312
2439
  version: "1.0.0",
2313
2440
  description: "Management API for OTA update publishing, deployment, and analytics"
@@ -2337,6 +2464,151 @@ var ProtocolApi = class extends HttpApi.make("protocol-api").add(ManifestGroup).
2337
2464
  description: "Expo Updates protocol endpoints (unauthenticated)"
2338
2465
  })) {};
2339
2466
 
2467
+ //#endregion
2468
+ //#region ../../packages/api/src/domain/policy-selector.ts
2469
+ /**
2470
+ * Pure, framework-agnostic validators for the IAM path-glob SELECTOR GRAMMAR and
2471
+ * the action-token shape. Shared by web / CLI / server so all three reject bad
2472
+ * input identically. This is INPUT-SHAPE validation only — it is distinct from
2473
+ * the server matching algorithm (`selectorMatches`), which is NOT duplicated here.
2474
+ */
2475
+ /** A selector segment: `*` or a non-empty token of `[A-Za-z0-9._:-]`. */
2476
+ const SEGMENT_PATTERN = /^[A-Za-z0-9._:-]+$/u;
2477
+ /**
2478
+ * An action token of `"<word>:<word>"` or `"<word>:*"`, where a word is a
2479
+ * non-empty run of `[A-Za-z0-9_-]`. The standalone `"*"` token is handled
2480
+ * separately in `isValidActionTokenShape`.
2481
+ */
2482
+ const ACTION_TOKEN_PATTERN = /^[A-Za-z0-9_-]+:(?:\*|[A-Za-z0-9_-]+)$/u;
2483
+ const isValidSegment = (segment) => segment === "*" || SEGMENT_PATTERN.test(segment);
2484
+ /**
2485
+ * A resource selector is valid when it is `"*"` OR is slash-joined segments where
2486
+ * each segment is `"*"` or a non-empty token of `[A-Za-z0-9._:-]`. Empty segments
2487
+ * (leading/trailing/double slashes) are rejected.
2488
+ */
2489
+ const isValidSelector = (selector) => {
2490
+ if (selector === "*") return true;
2491
+ if (selector.length === 0) return false;
2492
+ return selector.split("/").every(isValidSegment);
2493
+ };
2494
+ /**
2495
+ * An action token is valid in SHAPE when it is `"*"`, `"<word>:<word>"`, or
2496
+ * `"<word>:*"`. The server still validates the token against the real
2497
+ * resource/action vocabulary — this only guards the grammar.
2498
+ */
2499
+ const isValidActionTokenShape = (token) => token === "*" || ACTION_TOKEN_PATTERN.test(token);
2500
+ const ID = "@";
2501
+ const CANONICAL_TEMPLATES = [
2502
+ ["org"],
2503
+ ["project", ID],
2504
+ [
2505
+ "project",
2506
+ ID,
2507
+ "build"
2508
+ ],
2509
+ [
2510
+ "project",
2511
+ ID,
2512
+ "build",
2513
+ ID
2514
+ ],
2515
+ [
2516
+ "project",
2517
+ ID,
2518
+ "credential"
2519
+ ],
2520
+ [
2521
+ "project",
2522
+ ID,
2523
+ "credential",
2524
+ ID
2525
+ ],
2526
+ [
2527
+ "project",
2528
+ ID,
2529
+ "submission"
2530
+ ],
2531
+ [
2532
+ "project",
2533
+ ID,
2534
+ "submission",
2535
+ ID
2536
+ ],
2537
+ [
2538
+ "project",
2539
+ ID,
2540
+ "env",
2541
+ ID
2542
+ ],
2543
+ [
2544
+ "project",
2545
+ ID,
2546
+ "env",
2547
+ ID,
2548
+ "envVar"
2549
+ ],
2550
+ [
2551
+ "project",
2552
+ ID,
2553
+ "env",
2554
+ ID,
2555
+ "envVar",
2556
+ ID
2557
+ ],
2558
+ [
2559
+ "project",
2560
+ ID,
2561
+ "channel",
2562
+ ID
2563
+ ],
2564
+ [
2565
+ "project",
2566
+ ID,
2567
+ "channel",
2568
+ ID,
2569
+ "update"
2570
+ ],
2571
+ [
2572
+ "project",
2573
+ ID,
2574
+ "channel",
2575
+ ID,
2576
+ "update",
2577
+ ID
2578
+ ],
2579
+ [
2580
+ "project",
2581
+ ID,
2582
+ "channel",
2583
+ ID,
2584
+ "rollout"
2585
+ ],
2586
+ [
2587
+ "project",
2588
+ ID,
2589
+ "channel",
2590
+ ID,
2591
+ "rollout",
2592
+ ID
2593
+ ]
2594
+ ];
2595
+ const matchesTemplate = (template, segments) => template.length === segments.length && template.every((slot, index) => {
2596
+ const segment = segments[index];
2597
+ return segment !== void 0 && (slot === ID || segment === slot || segment === "*");
2598
+ });
2599
+ /**
2600
+ * True when a (shape-valid) selector matches one of the canonical resource-path
2601
+ * templates the server can actually produce — so a typo'd or pluralised segment
2602
+ * (e.g. `"project/A/channels/X"`) is caught at policy-write time instead of being
2603
+ * stored as a silently inert policy that can never match. The standalone `"*"`
2604
+ * matches everything and is always canonical.
2605
+ */
2606
+ const isCanonicalSelector = (selector) => {
2607
+ if (selector === "*") return true;
2608
+ const segments = selector.split("/");
2609
+ return CANONICAL_TEMPLATES.some((template) => matchesTemplate(template, segments));
2610
+ };
2611
+
2340
2612
  //#endregion
2341
2613
  //#region src/lib/exit-codes.ts
2342
2614
  var AuthRequiredError = class extends Data.TaggedError("AuthRequiredError") {};
@@ -2373,8 +2645,8 @@ var FingerprintMismatchError = class extends Data.TaggedError("FingerprintMismat
2373
2645
 
2374
2646
  //#endregion
2375
2647
  //#region ../../packages/type-guards/src/index.ts
2376
- const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2377
- const asRecord = (value) => isRecord(value) ? value : void 0;
2648
+ const isRecord$1 = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
2649
+ const asRecord = (value) => isRecord$1(value) ? value : void 0;
2378
2650
  const toOptional = (value) => value ?? void 0;
2379
2651
  const toDbNull = (value) => value ?? null;
2380
2652
  const compact = (obj) => Object.fromEntries(Object.entries(obj).filter(([, value]) => value !== void 0));
@@ -2432,7 +2704,7 @@ const AuthStoreLive = Layer.effect(AuthStore, Effect.gen(function* () {
2432
2704
  try: () => JSON.parse(content),
2433
2705
  catch: () => new AuthRequiredError({ message: "Corrupted auth file. Run `better-update login` to re-authenticate." })
2434
2706
  });
2435
- if (!isRecord(parsed)) return yield* new AuthRequiredError({ message: "Invalid auth file. Run `better-update login` to re-authenticate." });
2707
+ if (!isRecord$1(parsed)) return yield* new AuthRequiredError({ message: "Invalid auth file. Run `better-update login` to re-authenticate." });
2436
2708
  const { token } = parsed;
2437
2709
  if (typeof token !== "string") return yield* new AuthRequiredError({ message: "Invalid auth file. Run `better-update login` to re-authenticate." });
2438
2710
  return token;
@@ -2466,7 +2738,7 @@ const ConfigStoreLive = Layer.effect(ConfigStore, Effect.gen(function* () {
2466
2738
  message: "Config file contains invalid JSON",
2467
2739
  cause
2468
2740
  })
2469
- }).pipe(Effect.map((parsed) => isRecord(parsed) ? parsed : void 0), Effect.orElseSucceed(() => void 0))));
2741
+ }).pipe(Effect.map((parsed) => isRecord$1(parsed) ? parsed : void 0), Effect.orElseSucceed(() => void 0))));
2470
2742
  return {
2471
2743
  getBaseUrl: Effect.gen(function* () {
2472
2744
  const envUrl = yield* runtime.getEnv("BETTER_UPDATE_URL");
@@ -2692,7 +2964,7 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
2692
2964
  const content = yield* fs.readFileString(sessionFile).pipe(Effect.orElseSucceed(() => null));
2693
2965
  if (!content) return null;
2694
2966
  const parsed = safeJsonParse(content);
2695
- if (!isRecord(parsed)) return null;
2967
+ if (!isRecord$1(parsed)) return null;
2696
2968
  if (typeof parsed["username"] !== "string" || !parsed["cookies"]) return null;
2697
2969
  return {
2698
2970
  cookies: parsed["cookies"],
@@ -2710,7 +2982,7 @@ const AppleSessionStoreLive = Layer.effect(AppleSessionStore, Effect.gen(functio
2710
2982
  const content = yield* fs.readFileString(usernameFile).pipe(Effect.orElseSucceed(() => null));
2711
2983
  if (!content) return null;
2712
2984
  const parsed = safeJsonParse(content);
2713
- if (!isRecord(parsed) || typeof parsed["username"] !== "string") return null;
2985
+ if (!isRecord$1(parsed) || typeof parsed["username"] !== "string") return null;
2714
2986
  return parsed["username"];
2715
2987
  }),
2716
2988
  saveLastUsername: (username) => Effect.gen(function* () {
@@ -2893,13 +3165,13 @@ const BsdiffServiceLive = Layer.succeed(BsdiffService, { diff: (input) => Effect
2893
3165
 
2894
3166
  //#endregion
2895
3167
  //#region src/services/identity-store.ts
2896
- const isArgon2Params = (value) => isRecord(value) && typeof value["time"] === "number" && typeof value["memory"] === "number" && typeof value["parallelism"] === "number";
3168
+ const isArgon2Params = (value) => isRecord$1(value) && typeof value["time"] === "number" && typeof value["memory"] === "number" && typeof value["parallelism"] === "number";
2897
3169
  /**
2898
3170
  * Structural guard for the on-disk identity envelope. A corrupt or foreign file
2899
3171
  * reads as "absent" so the CLI prompts to (re)create rather than crashing — the
2900
3172
  * AAD-bound `openIdentity` still fails loudly if a well-formed file was tampered.
2901
3173
  */
2902
- const isIdentityFile = (value) => isRecord(value) && value["version"] === 1 && typeof value["publicKey"] === "string" && typeof value["fingerprint"] === "string" && value["kdf"] === "argon2id" && isArgon2Params(value["kdfParams"]) && typeof value["salt"] === "string" && value["cipher"] === "xchacha20poly1305" && typeof value["ct"] === "string";
3174
+ const isIdentityFile = (value) => isRecord$1(value) && value["version"] === 1 && typeof value["publicKey"] === "string" && typeof value["fingerprint"] === "string" && value["kdf"] === "argon2id" && isArgon2Params(value["kdfParams"]) && typeof value["salt"] === "string" && value["cipher"] === "xchacha20poly1305" && typeof value["ct"] === "string";
2903
3175
  var IdentityStore = class extends Context.Tag("cli/IdentityStore")() {};
2904
3176
  const IdentityStoreLive = Layer.effect(IdentityStore, Effect.gen(function* () {
2905
3177
  const fs = yield* FileSystem.FileSystem;
@@ -3100,7 +3372,7 @@ const UpdateAssetUploaderLive = Layer.effect(UpdateAssetUploader, Effect.gen(fun
3100
3372
  const VAULT_CACHE_TTL_MS = 900 * 1e3;
3101
3373
  /** Keychain service name; the account is the recipient's public key. */
3102
3374
  const KEYCHAIN_SERVICE = "better-update-vault";
3103
- const isCachedVaultEntry = (value) => isRecord(value) && typeof value["vaultKey"] === "string" && typeof value["vaultVersion"] === "number" && typeof value["keyId"] === "string" && typeof value["exp"] === "number";
3375
+ const isCachedVaultEntry = (value) => isRecord$1(value) && typeof value["vaultKey"] === "string" && typeof value["vaultVersion"] === "number" && typeof value["keyId"] === "string" && typeof value["exp"] === "number";
3104
3376
  /** Serialize an unlocked vault into a keychain blob, stamping a TTL from `now`. */
3105
3377
  const encodeCacheEntry = (vault, now, ttlMs = VAULT_CACHE_TTL_MS) => JSON.stringify({
3106
3378
  vaultKey: toBase64(vault.vaultKey),
@@ -3176,7 +3448,7 @@ const VersionCheckLive = Layer.effect(VersionCheck, Effect.gen(function* () {
3176
3448
  try: () => JSON.parse(content),
3177
3449
  catch: () => "parse-error"
3178
3450
  }).pipe(Effect.orElseSucceed(() => void 0));
3179
- if (isRecord(parsed) && typeof parsed["latest"] === "string" && typeof parsed["checkedAt"] === "number") return {
3451
+ if (isRecord$1(parsed) && typeof parsed["latest"] === "string" && typeof parsed["checkedAt"] === "number") return {
3180
3452
  latest: parsed["latest"],
3181
3453
  checkedAt: parsed["checkedAt"]
3182
3454
  };
@@ -3193,7 +3465,7 @@ const VersionCheckLive = Layer.effect(VersionCheck, Effect.gen(function* () {
3193
3465
  const response = yield* httpClient.execute(request);
3194
3466
  if (response.status < 200 || response.status >= 300) return;
3195
3467
  const body = yield* response.json;
3196
- if (!isRecord(body) || typeof body["version"] !== "string") return;
3468
+ if (!isRecord$1(body) || typeof body["version"] !== "string") return;
3197
3469
  const latest = body["version"];
3198
3470
  yield* fs.makeDirectory(cacheDir, { recursive: true });
3199
3471
  yield* fs.writeFileString(cacheFile, `${JSON.stringify({
@@ -3294,7 +3566,7 @@ const createBrowserLoginSession = (options = {}) => {
3294
3566
  if (request.method === "GET" && url.pathname === "/callback") return new Response(CALLBACK_PAGE, { headers: { "content-type": "text/html; charset=utf-8" } });
3295
3567
  if (request.method === "POST" && url.pathname === "/callback/token") try {
3296
3568
  const body = await request.json();
3297
- if (!isRecord(body)) return new Response("Invalid callback payload", { status: 400 });
3569
+ if (!isRecord$1(body)) return new Response("Invalid callback payload", { status: 400 });
3298
3570
  const token = typeof body["token"] === "string" ? body["token"].trim() : "";
3299
3571
  if (token.length === 0) return new Response("Missing token", { status: 400 });
3300
3572
  Effect.runSync(Deferred.succeed(tokenDeferred, token));
@@ -3777,7 +4049,7 @@ const configPath = (projectRoot) => path.join(projectRoot, BETTER_UPDATE_CONFIG_
3777
4049
  const readBetterUpdateConfig = (projectRoot) => Effect.gen(function* () {
3778
4050
  const content = yield* (yield* FileSystem.FileSystem).readFileString(configPath(projectRoot)).pipe(Effect.orElseSucceed(() => ""));
3779
4051
  if (content.length === 0) return;
3780
- return yield* Effect.try(() => JSON.parse(content)).pipe(Effect.map((parsed) => isRecord(parsed) ? parsed : void 0), Effect.orElseSucceed(() => void 0));
4052
+ return yield* Effect.try(() => JSON.parse(content)).pipe(Effect.map((parsed) => isRecord$1(parsed) ? parsed : void 0), Effect.orElseSucceed(() => void 0));
3781
4053
  });
3782
4054
  /**
3783
4055
  * Resolve the linked project id from `better-update.json`, or `undefined` when
@@ -4517,7 +4789,7 @@ const parseLimit = (raw, defaultValue) => {
4517
4789
 
4518
4790
  //#endregion
4519
4791
  //#region src/commands/audit-logs/list.ts
4520
- const listCommand$12 = defineCommand({
4792
+ const listCommand$9 = defineCommand({
4521
4793
  meta: {
4522
4794
  name: "list",
4523
4795
  description: "List audit log entries"
@@ -4579,7 +4851,7 @@ const auditLogsCommand = defineCommand({
4579
4851
  name: "audit-logs",
4580
4852
  description: "View audit logs"
4581
4853
  },
4582
- subCommands: { list: listCommand$12 }
4854
+ subCommands: { list: listCommand$9 }
4583
4855
  });
4584
4856
 
4585
4857
  //#endregion
@@ -4683,7 +4955,7 @@ const drainPages = (fetchPage) => {
4683
4955
 
4684
4956
  //#endregion
4685
4957
  //#region src/commands/branches.ts
4686
- const listCommand$11 = defineCommand({
4958
+ const listCommand$8 = defineCommand({
4687
4959
  meta: {
4688
4960
  name: "list",
4689
4961
  description: "List branches for the linked project"
@@ -4706,7 +4978,7 @@ const listCommand$11 = defineCommand({
4706
4978
  ]), "No branches found.");
4707
4979
  }))
4708
4980
  });
4709
- const createCommand$5 = defineCommand({
4981
+ const createCommand$4 = defineCommand({
4710
4982
  meta: {
4711
4983
  name: "create",
4712
4984
  description: "Create a branch"
@@ -4729,7 +5001,7 @@ const createCommand$5 = defineCommand({
4729
5001
  ]);
4730
5002
  }))
4731
5003
  });
4732
- const viewCommand$4 = defineCommand({
5004
+ const viewCommand$3 = defineCommand({
4733
5005
  meta: {
4734
5006
  name: "view",
4735
5007
  description: "Show a branch by ID or name"
@@ -4787,7 +5059,7 @@ const renameCommand$1 = defineCommand({
4787
5059
  return branch;
4788
5060
  }), { json: "value" })
4789
5061
  });
4790
- const deleteCommand$7 = defineCommand({
5062
+ const deleteCommand$6 = defineCommand({
4791
5063
  meta: {
4792
5064
  name: "delete",
4793
5065
  description: "Delete a branch"
@@ -4812,11 +5084,11 @@ const branchesCommand = defineCommand({
4812
5084
  description: "Manage branches"
4813
5085
  },
4814
5086
  subCommands: {
4815
- list: listCommand$11,
4816
- view: viewCommand$4,
4817
- create: createCommand$5,
5087
+ list: listCommand$8,
5088
+ view: viewCommand$3,
5089
+ create: createCommand$4,
4818
5090
  rename: renameCommand$1,
4819
- delete: deleteCommand$7
5091
+ delete: deleteCommand$6
4820
5092
  }
4821
5093
  });
4822
5094
 
@@ -18906,8 +19178,8 @@ var AscApiError = class extends Data.TaggedError("AscApiError") {};
18906
19178
  var AscNetworkError = class extends Data.TaggedError("AscNetworkError") {};
18907
19179
  const API_BASE = "https://api.appstoreconnect.apple.com";
18908
19180
  const extractErrors = (body) => {
18909
- if (!isRecord(body) || !Array.isArray(body["errors"])) return [];
18910
- return body["errors"].filter((value) => isRecord(value));
19181
+ if (!isRecord$1(body) || !Array.isArray(body["errors"])) return [];
19182
+ return body["errors"].filter((value) => isRecord$1(value));
18911
19183
  };
18912
19184
  const parseApiError = (response, body, raw) => {
18913
19185
  const [first] = extractErrors(body);
@@ -18943,9 +19215,9 @@ const fetchRaw = (jwt, path, init) => Effect.gen(function* () {
18943
19215
  return body;
18944
19216
  });
18945
19217
  const toAscCertificate = (value) => {
18946
- if (!isRecord(value)) return null;
19218
+ if (!isRecord$1(value)) return null;
18947
19219
  const { id, attributes } = value;
18948
- if (typeof id !== "string" || !isRecord(attributes)) return null;
19220
+ if (typeof id !== "string" || !isRecord$1(attributes)) return null;
18949
19221
  const { serialNumber, certificateType, expirationDate, certificateContent, displayName } = attributes;
18950
19222
  if (typeof serialNumber !== "string" || typeof certificateType !== "string" || typeof expirationDate !== "string") return null;
18951
19223
  return {
@@ -18958,9 +19230,9 @@ const toAscCertificate = (value) => {
18958
19230
  };
18959
19231
  };
18960
19232
  const toAscBundleId = (value) => {
18961
- if (!isRecord(value)) return null;
19233
+ if (!isRecord$1(value)) return null;
18962
19234
  const { id, attributes } = value;
18963
- if (typeof id !== "string" || !isRecord(attributes)) return null;
19235
+ if (typeof id !== "string" || !isRecord$1(attributes)) return null;
18964
19236
  const { identifier, name } = attributes;
18965
19237
  if (typeof identifier !== "string" || typeof name !== "string") return null;
18966
19238
  return {
@@ -18980,9 +19252,9 @@ const asProfileType = (value) => {
18980
19252
  return match === void 0 ? null : match;
18981
19253
  };
18982
19254
  const toAscProfile = (value) => {
18983
- if (!isRecord(value)) return null;
19255
+ if (!isRecord$1(value)) return null;
18984
19256
  const { id, attributes } = value;
18985
- if (typeof id !== "string" || !isRecord(attributes)) return null;
19257
+ if (typeof id !== "string" || !isRecord$1(attributes)) return null;
18986
19258
  const { name, uuid, expirationDate, profileContent } = attributes;
18987
19259
  const profileType = asProfileType(attributes["profileType"]);
18988
19260
  if (typeof name !== "string" || typeof uuid !== "string" || typeof expirationDate !== "string" || typeof profileContent !== "string" || profileType === null) return null;
@@ -18996,9 +19268,9 @@ const toAscProfile = (value) => {
18996
19268
  };
18997
19269
  };
18998
19270
  const toAscDevice = (value) => {
18999
- if (!isRecord(value)) return null;
19271
+ if (!isRecord$1(value)) return null;
19000
19272
  const { id, attributes } = value;
19001
- if (typeof id !== "string" || !isRecord(attributes)) return null;
19273
+ if (typeof id !== "string" || !isRecord$1(attributes)) return null;
19002
19274
  const { udid, name } = attributes;
19003
19275
  if (typeof udid !== "string" || typeof name !== "string") return null;
19004
19276
  return {
@@ -19008,11 +19280,11 @@ const toAscDevice = (value) => {
19008
19280
  };
19009
19281
  };
19010
19282
  const extractList = (body, map) => {
19011
- if (!isRecord(body) || !Array.isArray(body["data"])) return [];
19283
+ if (!isRecord$1(body) || !Array.isArray(body["data"])) return [];
19012
19284
  return body["data"].map(map).filter((value) => value !== null);
19013
19285
  };
19014
19286
  const extractSingle = (body, map) => {
19015
- if (!isRecord(body)) return null;
19287
+ if (!isRecord$1(body)) return null;
19016
19288
  return map(body["data"]);
19017
19289
  };
19018
19290
  const malformed = (resource) => new AscApiError({
@@ -22647,7 +22919,7 @@ const runFingerprintFull = (projectRoot, options = {}) => Effect.gen(function* (
22647
22919
  try: () => JSON.parse(stdout),
22648
22920
  catch: () => new FingerprintError({ message: "Failed to parse @expo/fingerprint output as JSON." })
22649
22921
  });
22650
- if (!isRecord(parsed)) return yield* new FingerprintError({ message: "@expo/fingerprint output was not a JSON object." });
22922
+ if (!isRecord$1(parsed)) return yield* new FingerprintError({ message: "@expo/fingerprint output was not a JSON object." });
22651
22923
  const { hash } = parsed;
22652
22924
  if (typeof hash !== "string" || hash.length === 0) return yield* new FingerprintError({ message: "@expo/fingerprint output did not contain a \"hash\" string field." });
22653
22925
  const sourcesRaw = parsed["sources"];
@@ -24264,7 +24536,7 @@ const compatibilityMatrixCommand = defineCommand({
24264
24536
 
24265
24537
  //#endregion
24266
24538
  //#region src/commands/builds/delete.ts
24267
- const deleteCommand$6 = defineCommand({
24539
+ const deleteCommand$5 = defineCommand({
24268
24540
  meta: {
24269
24541
  name: "delete",
24270
24542
  description: "Delete a build"
@@ -24410,7 +24682,7 @@ const DISTRIBUTION_OPTIONS$1 = [
24410
24682
  "play-store",
24411
24683
  "direct"
24412
24684
  ];
24413
- const listCommand$10 = defineCommand({
24685
+ const listCommand$7 = defineCommand({
24414
24686
  meta: {
24415
24687
  name: "list",
24416
24688
  description: "List builds for the linked project"
@@ -25068,9 +25340,9 @@ const buildsCommand = defineCommand({
25068
25340
  description: "Manage builds"
25069
25341
  },
25070
25342
  subCommands: {
25071
- list: listCommand$10,
25343
+ list: listCommand$7,
25072
25344
  get: getCommand$2,
25073
- delete: deleteCommand$6,
25345
+ delete: deleteCommand$5,
25074
25346
  download: downloadCommand$1,
25075
25347
  run: runCommand$1,
25076
25348
  "install-link": installLinkCommand,
@@ -25096,7 +25368,7 @@ const resolveNamedResourceId$1 = (params) => resolveNamedResourceId$2(params, (m
25096
25368
 
25097
25369
  //#endregion
25098
25370
  //#region src/commands/channels/create.ts
25099
- const createCommand$4 = defineCommand({
25371
+ const createCommand$3 = defineCommand({
25100
25372
  meta: {
25101
25373
  name: "create",
25102
25374
  description: "Create a channel"
@@ -25141,7 +25413,7 @@ const createCommand$4 = defineCommand({
25141
25413
 
25142
25414
  //#endregion
25143
25415
  //#region src/commands/channels/delete.ts
25144
- const deleteCommand$5 = defineCommand({
25416
+ const deleteCommand$4 = defineCommand({
25145
25417
  meta: {
25146
25418
  name: "delete",
25147
25419
  description: "Delete a channel"
@@ -25164,193 +25436,6 @@ const deleteCommand$5 = defineCommand({
25164
25436
  })
25165
25437
  });
25166
25438
 
25167
- //#endregion
25168
- //#region src/commands/channels/grants/helpers.ts
25169
- var GrantCommandError = class extends Data.TaggedError("GrantCommandError") {};
25170
- const grantErrorExtras = { GrantCommandError: 2 };
25171
-
25172
- //#endregion
25173
- //#region src/commands/channels/grants/list.ts
25174
- const resolveChannel = (channels, target) => Effect.gen(function* () {
25175
- const channel = channels.find((ch) => ch.id === target) ?? channels.find((ch) => ch.name === target);
25176
- if (!channel) return yield* new ChannelCommandError({ message: `Channel "${target}" not found by ID or name.` });
25177
- return channel;
25178
- });
25179
- const listCommand$9 = defineCommand({
25180
- meta: {
25181
- name: "list",
25182
- description: "List per-member grants on a channel"
25183
- },
25184
- args: { channel: {
25185
- type: "positional",
25186
- required: true,
25187
- description: "Channel ID or channel name"
25188
- } },
25189
- run: async ({ args }) => runEffect(Effect.gen(function* () {
25190
- const projectId = yield* readProjectId;
25191
- const api = yield* apiClient;
25192
- const channel = yield* resolveChannel(yield* drainPages((page) => api.channels.list({ urlParams: {
25193
- projectId,
25194
- limit: 100,
25195
- page
25196
- } })), args.channel);
25197
- yield* printList([
25198
- "ID",
25199
- "Member ID",
25200
- "Effect",
25201
- "Actions",
25202
- "Created"
25203
- ], (yield* api.channelGrants.list({
25204
- path: { id: channel.id },
25205
- urlParams: {}
25206
- })).map((grant) => [
25207
- grant.id,
25208
- grant.memberId,
25209
- grant.effect,
25210
- grant.actions.join(", "),
25211
- grant.createdAt
25212
- ]), "No grants found for this channel.");
25213
- }), { exits: {
25214
- ...channelErrorExtras,
25215
- ...grantErrorExtras
25216
- } })
25217
- });
25218
-
25219
- //#endregion
25220
- //#region src/commands/channels/grants/revoke.ts
25221
- const revokeCommand$2 = defineCommand({
25222
- meta: {
25223
- name: "revoke",
25224
- description: "Revoke all grants for a member on a channel"
25225
- },
25226
- args: {
25227
- channel: {
25228
- type: "positional",
25229
- required: true,
25230
- description: "Channel ID or channel name"
25231
- },
25232
- member: {
25233
- type: "string",
25234
- required: true,
25235
- description: "Member ID whose grants to revoke"
25236
- },
25237
- yes: {
25238
- type: "boolean",
25239
- description: "Skip confirmation prompt"
25240
- }
25241
- },
25242
- run: async ({ args }) => runEffect(Effect.gen(function* () {
25243
- if (!args.yes) {
25244
- if (!(yield* promptConfirm(`Revoke all grants for member ${args.member} on channel ${args.channel}?`, { initialValue: false }))) {
25245
- yield* printHuman("Cancelled.");
25246
- return { deleted: 0 };
25247
- }
25248
- }
25249
- const projectId = yield* readProjectId;
25250
- const api = yield* apiClient;
25251
- const channels = yield* drainPages((page) => api.channels.list({ urlParams: {
25252
- projectId,
25253
- limit: 100,
25254
- page
25255
- } }));
25256
- const channel = channels.find((ch) => ch.id === args.channel) ?? channels.find((ch) => ch.name === args.channel);
25257
- if (!channel) return yield* new ChannelCommandError({ message: `Channel "${args.channel}" not found by ID or name.` });
25258
- const result = yield* api.channelGrants.delete({ path: {
25259
- id: channel.id,
25260
- memberId: args.member
25261
- } });
25262
- yield* printHuman(`Revoked grants for member ${args.member} on channel "${channel.name}".`);
25263
- return result;
25264
- }), {
25265
- exits: {
25266
- ...channelErrorExtras,
25267
- ...grantErrorExtras
25268
- },
25269
- json: "value"
25270
- })
25271
- });
25272
-
25273
- //#endregion
25274
- //#region src/commands/channels/grants/set.ts
25275
- const setCommand$3 = defineCommand({
25276
- meta: {
25277
- name: "set",
25278
- description: "Create or replace a member's grant on a channel"
25279
- },
25280
- args: {
25281
- channel: {
25282
- type: "positional",
25283
- required: true,
25284
- description: "Channel ID or channel name"
25285
- },
25286
- member: {
25287
- type: "string",
25288
- required: true,
25289
- description: "Member ID to grant permissions to"
25290
- },
25291
- actions: {
25292
- type: "string",
25293
- required: true,
25294
- description: "Permission action tokens in resource:action format, comma-separated (e.g. update:create,rollout:update)"
25295
- },
25296
- effect: {
25297
- type: "string",
25298
- description: "Grant effect: allow (default) or deny"
25299
- }
25300
- },
25301
- run: async ({ args }) => runEffect(Effect.gen(function* () {
25302
- const effectValue = args.effect ?? "allow";
25303
- if (effectValue !== "allow" && effectValue !== "deny") return yield* new GrantCommandError({ message: `Invalid effect "${effectValue}" — must be "allow" or "deny".` });
25304
- const actionTokens = args.actions.split(",").map((tok) => tok.trim()).filter((tok) => tok.length > 0);
25305
- if (actionTokens.length === 0) return yield* new GrantCommandError({ message: "At least one action token is required." });
25306
- const projectId = yield* readProjectId;
25307
- const api = yield* apiClient;
25308
- const channels = yield* drainPages((page) => api.channels.list({ urlParams: {
25309
- projectId,
25310
- limit: 100,
25311
- page
25312
- } }));
25313
- const channel = channels.find((ch) => ch.id === args.channel) ?? channels.find((ch) => ch.name === args.channel);
25314
- if (!channel) return yield* new ChannelCommandError({ message: `Channel "${args.channel}" not found by ID or name.` });
25315
- const grant = yield* api.channelGrants.upsert({
25316
- path: {
25317
- id: channel.id,
25318
- memberId: args.member
25319
- },
25320
- payload: {
25321
- effect: effectValue,
25322
- actions: actionTokens
25323
- }
25324
- });
25325
- yield* printHumanKeyValue([
25326
- ["ID", grant.id],
25327
- ["Member ID", grant.memberId],
25328
- ["Channel ID", grant.scopeId],
25329
- ["Effect", grant.effect],
25330
- ["Actions", grant.actions.join(", ")],
25331
- ["Created", grant.createdAt]
25332
- ]);
25333
- return grant;
25334
- }), { exits: {
25335
- ...channelErrorExtras,
25336
- ...grantErrorExtras
25337
- } })
25338
- });
25339
-
25340
- //#endregion
25341
- //#region src/commands/channels/grants/index.ts
25342
- const grantsCommand$1 = defineCommand({
25343
- meta: {
25344
- name: "grants",
25345
- description: "Manage per-member permission grants on a channel"
25346
- },
25347
- subCommands: {
25348
- list: listCommand$9,
25349
- set: setCommand$3,
25350
- revoke: revokeCommand$2
25351
- }
25352
- });
25353
-
25354
25439
  //#endregion
25355
25440
  //#region src/commands/channels/insights.ts
25356
25441
  const insightsCommand$1 = defineCommand({
@@ -25397,7 +25482,7 @@ const insightsCommand$1 = defineCommand({
25397
25482
 
25398
25483
  //#endregion
25399
25484
  //#region src/commands/channels/list.ts
25400
- const listCommand$8 = defineCommand({
25485
+ const listCommand$6 = defineCommand({
25401
25486
  meta: {
25402
25487
  name: "list",
25403
25488
  description: "List channels for the linked project"
@@ -25501,7 +25586,7 @@ const completeCommand$1 = defineCommand({
25501
25586
 
25502
25587
  //#endregion
25503
25588
  //#region src/commands/channels/rollout/create.ts
25504
- const createCommand$3 = defineCommand({
25589
+ const createCommand$2 = defineCommand({
25505
25590
  meta: {
25506
25591
  name: "create",
25507
25592
  description: "Start a branch rollout on a channel"
@@ -25582,7 +25667,7 @@ const revertCommand$2 = defineCommand({
25582
25667
 
25583
25668
  //#endregion
25584
25669
  //#region src/commands/channels/rollout/update.ts
25585
- const updateCommand$4 = defineCommand({
25670
+ const updateCommand$3 = defineCommand({
25586
25671
  meta: {
25587
25672
  name: "update",
25588
25673
  description: "Update the rollout percentage on a channel"
@@ -25621,8 +25706,8 @@ const rolloutCommand$1 = defineCommand({
25621
25706
  description: "Manage channel branch rollouts"
25622
25707
  },
25623
25708
  subCommands: {
25624
- create: createCommand$3,
25625
- update: updateCommand$4,
25709
+ create: createCommand$2,
25710
+ update: updateCommand$3,
25626
25711
  complete: completeCommand$1,
25627
25712
  revert: revertCommand$2
25628
25713
  }
@@ -25630,7 +25715,7 @@ const rolloutCommand$1 = defineCommand({
25630
25715
 
25631
25716
  //#endregion
25632
25717
  //#region src/commands/channels/update.ts
25633
- const updateCommand$3 = defineCommand({
25718
+ const updateCommand$2 = defineCommand({
25634
25719
  meta: {
25635
25720
  name: "update",
25636
25721
  description: "Relink a channel to a different branch"
@@ -25673,7 +25758,7 @@ const updateCommand$3 = defineCommand({
25673
25758
 
25674
25759
  //#endregion
25675
25760
  //#region src/commands/channels/view.ts
25676
- const viewCommand$3 = defineCommand({
25761
+ const viewCommand$2 = defineCommand({
25677
25762
  meta: {
25678
25763
  name: "view",
25679
25764
  description: "Show a channel by ID or name"
@@ -25733,15 +25818,14 @@ const channelsCommand = defineCommand({
25733
25818
  description: "Manage channels"
25734
25819
  },
25735
25820
  subCommands: {
25736
- list: listCommand$8,
25737
- view: viewCommand$3,
25738
- create: createCommand$4,
25739
- update: updateCommand$3,
25821
+ list: listCommand$6,
25822
+ view: viewCommand$2,
25823
+ create: createCommand$3,
25824
+ update: updateCommand$2,
25740
25825
  pause: pauseCommand,
25741
25826
  resume: resumeCommand,
25742
- delete: deleteCommand$5,
25827
+ delete: deleteCommand$4,
25743
25828
  rollout: rolloutCommand$1,
25744
- grants: grantsCommand$1,
25745
25829
  insights: insightsCommand$1
25746
25830
  }
25747
25831
  });
@@ -27155,7 +27239,7 @@ const toRecipientView = (userEncryptionKeyId, key) => ({
27155
27239
  fingerprint: key?.fingerprint
27156
27240
  })
27157
27241
  });
27158
- const listCommand$7 = defineCommand({
27242
+ const listCommand$5 = defineCommand({
27159
27243
  meta: {
27160
27244
  name: "list",
27161
27245
  description: "List recipients that currently hold the org vault key"
@@ -27382,7 +27466,7 @@ const accessCommand = defineCommand({
27382
27466
  description: "Inspect, grant, rotate, revoke, and recover access to the org credential vault"
27383
27467
  },
27384
27468
  subCommands: {
27385
- list: listCommand$7,
27469
+ list: listCommand$5,
27386
27470
  grant: grantCommand,
27387
27471
  rotate: rotateCommand,
27388
27472
  revoke: revokeCommand$1,
@@ -27591,7 +27675,7 @@ const CREDENTIAL_TYPES$3 = [
27591
27675
  "keystore",
27592
27676
  "google-service-account-key"
27593
27677
  ];
27594
- const deleteCommand$4 = defineCommand({
27678
+ const deleteCommand$3 = defineCommand({
27595
27679
  meta: {
27596
27680
  name: "delete",
27597
27681
  description: "Delete a credential"
@@ -27633,7 +27717,7 @@ const deleteCommand$4 = defineCommand({
27633
27717
  //#region src/commands/credentials/device.ts
27634
27718
  /** Self-linking is for your own device keys; recovery/machine keys go through `access grant`. */
27635
27719
  const requireDeviceKind = (target) => target.kind === "device" ? Effect.void : new IdentityError({ message: `Key ${target.id} is a ${target.kind} key, not a device. Use \`better-update credentials access grant\` for recovery/machine keys.` });
27636
- const listCommand$6 = defineCommand({
27720
+ const listCommand$4 = defineCommand({
27637
27721
  meta: {
27638
27722
  name: "list",
27639
27723
  description: "List your registered device keys (the active one is marked)"
@@ -27691,7 +27775,7 @@ const deviceCommand = defineCommand({
27691
27775
  description: "Manage your vault device keys"
27692
27776
  },
27693
27777
  subCommands: {
27694
- list: listCommand$6,
27778
+ list: listCommand$4,
27695
27779
  link: linkCommand
27696
27780
  },
27697
27781
  default: "list"
@@ -28346,7 +28430,7 @@ const printRecipient = (key) => printKeyValue([
28346
28430
  ["Recipient (public key)", key.publicKey],
28347
28431
  ["Fingerprint", key.fingerprint]
28348
28432
  ]);
28349
- const createCommand$2 = defineCommand({
28433
+ const createCommand$1 = defineCommand({
28350
28434
  meta: {
28351
28435
  name: "create",
28352
28436
  description: "Create this device's encryption identity and register it as a recipient"
@@ -28449,7 +28533,7 @@ const identityCommand = defineCommand({
28449
28533
  description: "Manage this device's end-to-end encryption identity"
28450
28534
  },
28451
28535
  subCommands: {
28452
- create: createCommand$2,
28536
+ create: createCommand$1,
28453
28537
  init: initCommand$1,
28454
28538
  register: registerCommand,
28455
28539
  show: showCommand
@@ -28459,7 +28543,7 @@ const identityCommand = defineCommand({
28459
28543
 
28460
28544
  //#endregion
28461
28545
  //#region src/commands/credentials/list.ts
28462
- const listCommand$5 = defineCommand({
28546
+ const listCommand$3 = defineCommand({
28463
28547
  meta: {
28464
28548
  name: "list",
28465
28549
  description: "List credentials across platforms"
@@ -29614,7 +29698,7 @@ const lookupByType = (api, id, type) => {
29614
29698
  default: return Effect.fail(new CredentialValidationError({ message: `Unsupported credential type: ${String(type)}` }));
29615
29699
  }
29616
29700
  };
29617
- const viewCommand$2 = defineCommand({
29701
+ const viewCommand$1 = defineCommand({
29618
29702
  meta: {
29619
29703
  name: "view",
29620
29704
  description: "Show details for a single credential (without secrets)"
@@ -29661,14 +29745,14 @@ const credentialsCommand = defineCommand({
29661
29745
  unlock: unlockCommand,
29662
29746
  lock: lockCommand,
29663
29747
  status: statusCommand$1,
29664
- list: listCommand$5,
29665
- view: viewCommand$2,
29748
+ list: listCommand$3,
29749
+ view: viewCommand$1,
29666
29750
  download: downloadCommand,
29667
29751
  upload: uploadCommand,
29668
29752
  "upload-asc-key": uploadAscKeyCommand,
29669
29753
  generate: generateCommand$1,
29670
29754
  "regenerate-profile": regenerateProfileCommand,
29671
- delete: deleteCommand$4,
29755
+ delete: deleteCommand$3,
29672
29756
  remove: removeCommand,
29673
29757
  revoke: revokeCommand,
29674
29758
  configure: configureCommand$1,
@@ -30168,19 +30252,19 @@ const envErrorExtras = {
30168
30252
  SystemError: 6,
30169
30253
  BadArgument: 6
30170
30254
  };
30171
- const isEnvironmentName$1 = (value) => value === "development" || value === "preview" || value === "production";
30255
+ const isEnvironmentName = (value) => value === "development" || value === "preview" || value === "production";
30172
30256
  const parseEnvironmentsArg = (raw) => Effect.gen(function* () {
30173
30257
  const tokens = raw.split(",").map((token) => token.trim()).filter((token) => token.length > 0);
30174
30258
  if (tokens.length === 0) return yield* new InvalidArgumentError({ message: "Provide at least one environment (development, preview, production)." });
30175
30259
  const seen = /* @__PURE__ */ new Set();
30176
30260
  yield* Effect.forEach(tokens, (token) => Effect.gen(function* () {
30177
- if (!isEnvironmentName$1(token)) return yield* new InvalidArgumentError({ message: `Invalid environment "${token}". Must be one of: development, preview, production.` });
30261
+ if (!isEnvironmentName(token)) return yield* new InvalidArgumentError({ message: `Invalid environment "${token}". Must be one of: development, preview, production.` });
30178
30262
  seen.add(token);
30179
30263
  }), { discard: true });
30180
30264
  return [...seen];
30181
30265
  });
30182
30266
  const parseSingleEnvironmentArg = (raw) => Effect.gen(function* () {
30183
- if (!isEnvironmentName$1(raw)) return yield* new InvalidArgumentError({ message: `Invalid environment "${raw}". Must be one of: development, preview, production.` });
30267
+ if (!isEnvironmentName(raw)) return yield* new InvalidArgumentError({ message: `Invalid environment "${raw}". Must be one of: development, preview, production.` });
30184
30268
  return raw;
30185
30269
  });
30186
30270
  const formatEnvironments = (environments) => [...environments].toSorted((left, right) => left.localeCompare(right)).join(",");
@@ -30219,7 +30303,7 @@ const findProjectEnvVar = (api, projectId, key, environment) => Effect.gen(funct
30219
30303
 
30220
30304
  //#endregion
30221
30305
  //#region src/commands/env/delete.ts
30222
- const deleteCommand$3 = defineCommand({
30306
+ const deleteCommand$2 = defineCommand({
30223
30307
  meta: {
30224
30308
  name: "delete",
30225
30309
  description: "Delete a project env var (one environment, or every environment by default)"
@@ -30391,191 +30475,6 @@ const getCommand$1 = defineCommand({
30391
30475
  }), envErrorExtras)
30392
30476
  });
30393
30477
 
30394
- //#endregion
30395
- //#region src/commands/env/grants/helpers.ts
30396
- var EnvGrantCommandError = class extends Data.TaggedError("EnvGrantCommandError") {};
30397
- const envGrantErrorExtras = { EnvGrantCommandError: 2 };
30398
- /** Sentinel project token for the org-global env-var scope (mirrors server). */
30399
- const ENV_GRANT_GLOBAL = "global";
30400
- const ENVIRONMENTS = [
30401
- "development",
30402
- "preview",
30403
- "production"
30404
- ];
30405
- /**
30406
- * Type guard narrowing a raw arg to an {@link EnvironmentName}. Lets set/unset
30407
- * validate `args.environment` AND narrow it without an unsafe `as` assertion.
30408
- */
30409
- const isEnvironmentName = (value) => ENVIRONMENTS.includes(value);
30410
-
30411
- //#endregion
30412
- //#region src/commands/env/grants/list.ts
30413
- const listCommand$4 = defineCommand({
30414
- meta: {
30415
- name: "list",
30416
- description: "List env-var grants on a (project × environment) scope"
30417
- },
30418
- args: {
30419
- project: {
30420
- type: "string",
30421
- description: `Project id, or "${ENV_GRANT_GLOBAL}" for the org-global scope (default: linked project)`
30422
- },
30423
- global: {
30424
- type: "boolean",
30425
- default: false,
30426
- description: "Target the org-global env-var scope instead of a project"
30427
- }
30428
- },
30429
- run: async ({ args }) => runEffect(Effect.gen(function* () {
30430
- const projectId = args.global ? ENV_GRANT_GLOBAL : args.project ?? (yield* readProjectId);
30431
- yield* printList([
30432
- "Member ID",
30433
- "Environment",
30434
- "Effect",
30435
- "Actions"
30436
- ], (yield* (yield* apiClient).envGrants.list({ urlParams: { projectId } })).map((row) => [
30437
- row.memberId,
30438
- row.environment,
30439
- row.effect,
30440
- row.actions.join(", ")
30441
- ]), "No env-var grants found for this scope.");
30442
- }), { exits: { ...envGrantErrorExtras } })
30443
- });
30444
-
30445
- //#endregion
30446
- //#region src/commands/env/grants/set.ts
30447
- const setCommand$2 = defineCommand({
30448
- meta: {
30449
- name: "set",
30450
- description: "Create or replace a member's env-var grant on a scope"
30451
- },
30452
- args: {
30453
- member: {
30454
- type: "string",
30455
- required: true,
30456
- description: "Member ID to grant"
30457
- },
30458
- environment: {
30459
- type: "string",
30460
- required: true,
30461
- description: "Environment: development | preview | production"
30462
- },
30463
- actions: {
30464
- type: "string",
30465
- description: "Comma-separated envVar:* tokens (default: envVar:read)"
30466
- },
30467
- effect: {
30468
- type: "string",
30469
- description: "allow (default) or deny"
30470
- },
30471
- project: {
30472
- type: "string",
30473
- description: `Project id (default: linked project)`
30474
- },
30475
- global: {
30476
- type: "boolean",
30477
- default: false,
30478
- description: "Target the org-global env-var scope"
30479
- }
30480
- },
30481
- run: async ({ args }) => runEffect(Effect.gen(function* () {
30482
- const effectValue = args.effect ?? "allow";
30483
- if (effectValue !== "allow" && effectValue !== "deny") return yield* new EnvGrantCommandError({ message: `Invalid effect "${effectValue}".` });
30484
- const { environment } = args;
30485
- if (!isEnvironmentName(environment)) return yield* new EnvGrantCommandError({ message: `Invalid environment "${environment}". One of: ${ENVIRONMENTS.join(", ")}.` });
30486
- const actionTokens = (args.actions ?? "envVar:read").split(",").map((tok) => tok.trim()).filter((tok) => tok.length > 0);
30487
- if (actionTokens.length === 0) return yield* new EnvGrantCommandError({ message: "At least one action token is required." });
30488
- const projectId = args.global ? null : args.project ?? (yield* readProjectId);
30489
- const grant = yield* (yield* apiClient).envGrants.upsert({ payload: {
30490
- memberId: args.member,
30491
- projectId,
30492
- environment,
30493
- effect: effectValue,
30494
- actions: actionTokens
30495
- } });
30496
- yield* printHumanKeyValue([
30497
- ["ID", grant.id],
30498
- ["Member ID", grant.memberId],
30499
- ["Scope", grant.scopeId],
30500
- ["Effect", grant.effect],
30501
- ["Actions", grant.actions.join(", ")],
30502
- ["Created", grant.createdAt]
30503
- ]);
30504
- return grant;
30505
- }), { exits: { ...envGrantErrorExtras } })
30506
- });
30507
-
30508
- //#endregion
30509
- //#region src/commands/env/grants/unset.ts
30510
- const unsetCommand = defineCommand({
30511
- meta: {
30512
- name: "unset",
30513
- description: "Revoke a member's env-var grants on a scope"
30514
- },
30515
- args: {
30516
- member: {
30517
- type: "string",
30518
- required: true,
30519
- description: "Member ID whose grants to revoke"
30520
- },
30521
- environment: {
30522
- type: "string",
30523
- required: true,
30524
- description: "Environment"
30525
- },
30526
- project: {
30527
- type: "string",
30528
- description: "Project id (default: linked project)"
30529
- },
30530
- global: {
30531
- type: "boolean",
30532
- default: false,
30533
- description: "Target the org-global scope"
30534
- },
30535
- yes: {
30536
- type: "boolean",
30537
- default: false,
30538
- description: "Skip confirmation prompt"
30539
- }
30540
- },
30541
- run: async ({ args }) => runEffect(Effect.gen(function* () {
30542
- const { environment } = args;
30543
- if (!isEnvironmentName(environment)) return yield* new EnvGrantCommandError({ message: `Invalid environment "${environment}".` });
30544
- if (!args.yes) {
30545
- const scopeLabel = args.global ? ENV_GRANT_GLOBAL : args.project ?? "linked project";
30546
- if (!(yield* promptConfirm(`Revoke env-var grants for member ${args.member} on ${scopeLabel}/${args.environment}?`, { initialValue: false }))) {
30547
- yield* printHuman("Cancelled.");
30548
- return { deleted: 0 };
30549
- }
30550
- }
30551
- const projectId = args.global ? null : args.project ?? (yield* readProjectId);
30552
- const result = yield* (yield* apiClient).envGrants.delete({ payload: {
30553
- memberId: args.member,
30554
- projectId,
30555
- environment
30556
- } });
30557
- yield* printHuman(`Revoked env-var grants for member ${args.member}.`);
30558
- return result;
30559
- }), {
30560
- exits: { ...envGrantErrorExtras },
30561
- json: "value"
30562
- })
30563
- });
30564
-
30565
- //#endregion
30566
- //#region src/commands/env/grants/index.ts
30567
- const grantsCommand = defineCommand({
30568
- meta: {
30569
- name: "grants",
30570
- description: "Manage per-member env-var access grants on a (project × environment) scope"
30571
- },
30572
- subCommands: {
30573
- list: listCommand$4,
30574
- set: setCommand$2,
30575
- unset: unsetCommand
30576
- }
30577
- });
30578
-
30579
30478
  //#endregion
30580
30479
  //#region src/commands/env/history.ts
30581
30480
  const historyCommand = defineCommand({
@@ -30674,7 +30573,7 @@ const importCommand = defineCommand({
30674
30573
 
30675
30574
  //#endregion
30676
30575
  //#region src/commands/env/list.ts
30677
- const listCommand$3 = defineCommand({
30576
+ const listCommand$2 = defineCommand({
30678
30577
  meta: {
30679
30578
  name: "list",
30680
30579
  description: "List environment variable metadata. Values are end-to-end encrypted — read them with `env pull`, `env export`, or `env get`."
@@ -30940,7 +30839,7 @@ const setCommand$1 = defineCommand({
30940
30839
 
30941
30840
  //#endregion
30942
30841
  //#region src/commands/env/update.ts
30943
- const updateCommand$2 = defineCommand({
30842
+ const updateCommand$1 = defineCommand({
30944
30843
  meta: {
30945
30844
  name: "update",
30946
30845
  description: "Update a project env var's value or visibility for an environment"
@@ -31021,19 +30920,18 @@ const envCommand = defineCommand({
31021
30920
  description: "Manage environment variables"
31022
30921
  },
31023
30922
  subCommands: {
31024
- list: listCommand$3,
30923
+ list: listCommand$2,
31025
30924
  get: getCommand$1,
31026
30925
  set: setCommand$1,
31027
- update: updateCommand$2,
31028
- delete: deleteCommand$3,
30926
+ update: updateCommand$1,
30927
+ delete: deleteCommand$2,
31029
30928
  history: historyCommand,
31030
30929
  rollback: rollbackCommand$1,
31031
30930
  import: importCommand,
31032
30931
  push: pushCommand,
31033
30932
  export: exportCommand,
31034
30933
  pull: pullCommand,
31035
- exec: execCommand,
31036
- grants: grantsCommand
30934
+ exec: execCommand
31037
30935
  }
31038
30936
  });
31039
30937
 
@@ -31299,6 +31197,376 @@ const fingerprintCommand = defineCommand({
31299
31197
  }
31300
31198
  });
31301
31199
 
31200
+ //#endregion
31201
+ //#region src/commands/groups/helpers.ts
31202
+ const groupErrorExtras = { GroupCommandError: 2 };
31203
+
31204
+ //#endregion
31205
+ //#region src/commands/groups/attach.ts
31206
+ const attachGroupPolicyCommand = defineCommand({
31207
+ meta: {
31208
+ name: "attach",
31209
+ description: "Attach a policy (real or managed:*) to a group; members inherit it"
31210
+ },
31211
+ args: {
31212
+ id: {
31213
+ type: "positional",
31214
+ required: true,
31215
+ description: "Group ID"
31216
+ },
31217
+ "policy-id": {
31218
+ type: "string",
31219
+ required: true,
31220
+ description: "Policy ID to attach (real id or managed preset like managed:admin)"
31221
+ }
31222
+ },
31223
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31224
+ const attachment = yield* (yield* apiClient)["policy-attachments"].attachToGroup({
31225
+ path: { id: args.id },
31226
+ payload: { policyId: args["policy-id"] }
31227
+ });
31228
+ yield* printHumanKeyValue([
31229
+ ["Attachment ID", attachment.id],
31230
+ ["Group ID", attachment.principalId],
31231
+ ["Policy ID", attachment.policyId],
31232
+ ["Created", attachment.createdAt]
31233
+ ]);
31234
+ return attachment;
31235
+ }), {
31236
+ exits: groupErrorExtras,
31237
+ json: "value"
31238
+ })
31239
+ });
31240
+
31241
+ //#endregion
31242
+ //#region src/commands/groups/create.ts
31243
+ const createGroupCommand = defineCommand({
31244
+ meta: {
31245
+ name: "create",
31246
+ description: "Create a member group"
31247
+ },
31248
+ args: {
31249
+ name: {
31250
+ type: "string",
31251
+ required: true,
31252
+ description: "Group display name"
31253
+ },
31254
+ description: {
31255
+ type: "string",
31256
+ description: "Optional human description"
31257
+ }
31258
+ },
31259
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31260
+ const group = yield* (yield* apiClient).groups.create({ payload: {
31261
+ name: args.name,
31262
+ ...compact({ description: args.description })
31263
+ } });
31264
+ yield* printHumanKeyValue([
31265
+ ["ID", group.id],
31266
+ ["Name", group.name],
31267
+ ["Description", group.description ?? "-"],
31268
+ ["Created", group.createdAt]
31269
+ ]);
31270
+ return group;
31271
+ }), {
31272
+ exits: groupErrorExtras,
31273
+ json: "value"
31274
+ })
31275
+ });
31276
+
31277
+ //#endregion
31278
+ //#region src/commands/groups/delete.ts
31279
+ const deleteGroupCommand = defineCommand({
31280
+ meta: {
31281
+ name: "delete",
31282
+ description: "Delete a group and sweep its memberships and policy attachments"
31283
+ },
31284
+ args: {
31285
+ id: {
31286
+ type: "positional",
31287
+ required: true,
31288
+ description: "Group ID"
31289
+ },
31290
+ yes: {
31291
+ type: "boolean",
31292
+ description: "Skip confirmation prompt"
31293
+ }
31294
+ },
31295
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31296
+ if (!args.yes) {
31297
+ if (!(yield* promptConfirm(`Delete group ${args.id}?`, { initialValue: false }))) {
31298
+ yield* printHuman("Cancelled.");
31299
+ return;
31300
+ }
31301
+ }
31302
+ yield* (yield* apiClient).groups.delete({ path: { id: args.id } });
31303
+ yield* printHuman(`Deleted group ${args.id}.`);
31304
+ return {
31305
+ id: args.id,
31306
+ deleted: true
31307
+ };
31308
+ }), {
31309
+ exits: groupErrorExtras,
31310
+ json: "value"
31311
+ })
31312
+ });
31313
+
31314
+ //#endregion
31315
+ //#region src/commands/groups/detach.ts
31316
+ const detachGroupPolicyCommand = defineCommand({
31317
+ meta: {
31318
+ name: "detach",
31319
+ description: "Remove a policy attachment from a group"
31320
+ },
31321
+ args: {
31322
+ id: {
31323
+ type: "positional",
31324
+ required: true,
31325
+ description: "Group ID"
31326
+ },
31327
+ "policy-id": {
31328
+ type: "string",
31329
+ required: true,
31330
+ description: "Policy ID to detach (real id or managed preset like managed:admin)"
31331
+ }
31332
+ },
31333
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31334
+ yield* (yield* apiClient)["policy-attachments"].detachFromGroup({ path: {
31335
+ id: args.id,
31336
+ policyId: encodeURIComponent(args["policy-id"])
31337
+ } });
31338
+ yield* printHuman(`Detached policy ${args["policy-id"]} from group ${args.id}.`);
31339
+ return {
31340
+ groupId: args.id,
31341
+ policyId: args["policy-id"],
31342
+ detached: true
31343
+ };
31344
+ }), {
31345
+ exits: groupErrorExtras,
31346
+ json: "value"
31347
+ })
31348
+ });
31349
+
31350
+ //#endregion
31351
+ //#region src/commands/groups/list.ts
31352
+ const listGroupsCommand = defineCommand({
31353
+ meta: {
31354
+ name: "list",
31355
+ description: "List member groups in the active organization"
31356
+ },
31357
+ run: async () => runEffect(Effect.gen(function* () {
31358
+ const result = yield* (yield* apiClient).groups.list();
31359
+ yield* printHumanTable([
31360
+ "ID",
31361
+ "Name",
31362
+ "Description",
31363
+ "Created"
31364
+ ], result.items.map((group) => [
31365
+ group.id,
31366
+ group.name,
31367
+ group.description ?? "-",
31368
+ group.createdAt
31369
+ ]));
31370
+ return result;
31371
+ }), {
31372
+ exits: groupErrorExtras,
31373
+ json: "value"
31374
+ })
31375
+ });
31376
+
31377
+ //#endregion
31378
+ //#region src/commands/groups/members/add.ts
31379
+ const addGroupMemberCommand = defineCommand({
31380
+ meta: {
31381
+ name: "add",
31382
+ description: "Add an organization member to a group"
31383
+ },
31384
+ args: {
31385
+ id: {
31386
+ type: "positional",
31387
+ required: true,
31388
+ description: "Group ID"
31389
+ },
31390
+ "member-id": {
31391
+ type: "string",
31392
+ required: true,
31393
+ description: "Organization member ID to add"
31394
+ }
31395
+ },
31396
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31397
+ const member = yield* (yield* apiClient).groups.addMember({
31398
+ path: { id: args.id },
31399
+ payload: { memberId: args["member-id"] }
31400
+ });
31401
+ yield* printHumanKeyValue([["Member ID", member.memberId], ["Added", member.createdAt]]);
31402
+ return member;
31403
+ }), {
31404
+ exits: groupErrorExtras,
31405
+ json: "value"
31406
+ })
31407
+ });
31408
+
31409
+ //#endregion
31410
+ //#region src/commands/groups/members/remove.ts
31411
+ const removeGroupMemberCommand = defineCommand({
31412
+ meta: {
31413
+ name: "remove",
31414
+ description: "Remove a member from a group"
31415
+ },
31416
+ args: {
31417
+ id: {
31418
+ type: "positional",
31419
+ required: true,
31420
+ description: "Group ID"
31421
+ },
31422
+ "member-id": {
31423
+ type: "string",
31424
+ required: true,
31425
+ description: "Organization member ID to remove"
31426
+ }
31427
+ },
31428
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31429
+ yield* (yield* apiClient).groups.removeMember({ path: {
31430
+ id: args.id,
31431
+ memberId: args["member-id"]
31432
+ } });
31433
+ yield* printHuman(`Removed member ${args["member-id"]} from group ${args.id}.`);
31434
+ return {
31435
+ groupId: args.id,
31436
+ memberId: args["member-id"],
31437
+ removed: true
31438
+ };
31439
+ }), {
31440
+ exits: groupErrorExtras,
31441
+ json: "value"
31442
+ })
31443
+ });
31444
+
31445
+ //#endregion
31446
+ //#region src/commands/groups/members/index.ts
31447
+ const listGroupMembersCommand = defineCommand({
31448
+ meta: {
31449
+ name: "list",
31450
+ description: "List the members belonging to a group"
31451
+ },
31452
+ args: { id: {
31453
+ type: "positional",
31454
+ required: true,
31455
+ description: "Group ID"
31456
+ } },
31457
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31458
+ const result = yield* (yield* apiClient).groups.listMembers({ path: { id: args.id } });
31459
+ yield* printHumanTable(["Member ID", "Added"], result.items.map((member) => [member.memberId, member.createdAt]));
31460
+ return result;
31461
+ }), {
31462
+ exits: groupErrorExtras,
31463
+ json: "value"
31464
+ })
31465
+ });
31466
+ const membersCommand = defineCommand({
31467
+ meta: {
31468
+ name: "members",
31469
+ description: "Manage the members of a group"
31470
+ },
31471
+ subCommands: {
31472
+ list: listGroupMembersCommand,
31473
+ add: addGroupMemberCommand,
31474
+ remove: removeGroupMemberCommand
31475
+ }
31476
+ });
31477
+
31478
+ //#endregion
31479
+ //#region src/commands/groups/policies.ts
31480
+ const listGroupPoliciesCommand = defineCommand({
31481
+ meta: {
31482
+ name: "policies",
31483
+ description: "List policies attached to a group"
31484
+ },
31485
+ args: { id: {
31486
+ type: "positional",
31487
+ required: true,
31488
+ description: "Group ID"
31489
+ } },
31490
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31491
+ const result = yield* (yield* apiClient)["policy-attachments"].listForGroup({ path: { id: args.id } });
31492
+ yield* printHumanTable([
31493
+ "Attachment ID",
31494
+ "Policy ID",
31495
+ "Created"
31496
+ ], result.items.map((attachment) => [
31497
+ attachment.id,
31498
+ attachment.policyId,
31499
+ attachment.createdAt
31500
+ ]));
31501
+ return result;
31502
+ }), {
31503
+ exits: groupErrorExtras,
31504
+ json: "value"
31505
+ })
31506
+ });
31507
+
31508
+ //#endregion
31509
+ //#region src/commands/groups/update.ts
31510
+ const updateGroupCommand = defineCommand({
31511
+ meta: {
31512
+ name: "update",
31513
+ description: "Update a group's name or description"
31514
+ },
31515
+ args: {
31516
+ id: {
31517
+ type: "positional",
31518
+ required: true,
31519
+ description: "Group ID"
31520
+ },
31521
+ name: {
31522
+ type: "string",
31523
+ description: "New display name"
31524
+ },
31525
+ description: {
31526
+ type: "string",
31527
+ description: "New description"
31528
+ }
31529
+ },
31530
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31531
+ const group = yield* (yield* apiClient).groups.update({
31532
+ path: { id: args.id },
31533
+ payload: compact({
31534
+ name: args.name,
31535
+ description: args.description
31536
+ })
31537
+ });
31538
+ yield* printHumanKeyValue([
31539
+ ["ID", group.id],
31540
+ ["Name", group.name],
31541
+ ["Description", group.description ?? "-"],
31542
+ ["Updated", group.updatedAt ?? "-"]
31543
+ ]);
31544
+ return group;
31545
+ }), {
31546
+ exits: groupErrorExtras,
31547
+ json: "value"
31548
+ })
31549
+ });
31550
+
31551
+ //#endregion
31552
+ //#region src/commands/groups/index.ts
31553
+ const groupsCommand = defineCommand({
31554
+ meta: {
31555
+ name: "groups",
31556
+ description: "Manage member groups for collective policy attachment"
31557
+ },
31558
+ subCommands: {
31559
+ list: listGroupsCommand,
31560
+ create: createGroupCommand,
31561
+ update: updateGroupCommand,
31562
+ delete: deleteGroupCommand,
31563
+ members: membersCommand,
31564
+ policies: listGroupPoliciesCommand,
31565
+ attach: attachGroupPolicyCommand,
31566
+ detach: detachGroupPolicyCommand
31567
+ }
31568
+ });
31569
+
31302
31570
  //#endregion
31303
31571
  //#region src/commands/init.ts
31304
31572
  const checkExistingLink = (api, config, localSlug) => Effect.gen(function* () {
@@ -31327,7 +31595,7 @@ const readPackageJsonName = (projectRoot) => Effect.gen(function* () {
31327
31595
  const content = yield* (yield* FileSystem.FileSystem).readFileString(path.join(projectRoot, "package.json")).pipe(Effect.orElseSucceed(() => ""));
31328
31596
  if (content.length === 0) return;
31329
31597
  const parsed = yield* Effect.try(() => JSON.parse(content)).pipe(Effect.orElseSucceed(() => void 0));
31330
- const name = isRecord(parsed) ? parsed["name"] : void 0;
31598
+ const name = isRecord$1(parsed) ? parsed["name"] : void 0;
31331
31599
  return typeof name === "string" && name.length > 0 ? name : void 0;
31332
31600
  });
31333
31601
  /**
@@ -31568,9 +31836,239 @@ const openCommand = defineCommand({
31568
31836
  }))
31569
31837
  });
31570
31838
 
31839
+ //#endregion
31840
+ //#region src/commands/policies/helpers.ts
31841
+ var PolicyCommandError = class extends Data.TaggedError("PolicyCommandError") {};
31842
+ const policyErrorExtras = { PolicyCommandError: 2 };
31843
+ /** A managed preset id is virtual + read-only; its id is prefixed with `managed:`. */
31844
+ const isManagedPolicyId = (id) => id.startsWith("managed:");
31845
+ const isStringArray = (value) => Array.isArray(value) && value.every((entry) => typeof entry === "string");
31846
+ const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
31847
+ /**
31848
+ * Parse + shape-validate a `--document` JSON string client-side so a malformed
31849
+ * document fails with a clear local message before any network round-trip. The
31850
+ * server still re-validates action tokens against the real vocabulary and the
31851
+ * selectors against the shared grammar; this only guards the JSON shape and the
31852
+ * action/selector token SHAPE via the contract's pure validators.
31853
+ *
31854
+ * Expected shape:
31855
+ * { "statements": [ { "effect": "allow"|"deny", "actions": ["project:read"|"*"], "resources": ["*"|"project/A"] } ] }
31856
+ */
31857
+ const parsePolicyDocument = (raw) => Effect.gen(function* () {
31858
+ const parsed = yield* Effect.try({
31859
+ try: () => JSON.parse(raw),
31860
+ catch: () => new PolicyCommandError({ message: "The --document value is not valid JSON. Pass a JSON object string." })
31861
+ });
31862
+ if (!isRecord(parsed) || !Array.isArray(parsed["statements"])) return yield* new PolicyCommandError({ message: "The document must be a JSON object with a \"statements\" array. Example: {\"statements\":[{\"effect\":\"allow\",\"actions\":[\"project:read\"],\"resources\":[\"*\"]}]}" });
31863
+ const statements = [];
31864
+ for (const [index, entry] of parsed["statements"].entries()) {
31865
+ const statement = yield* validateStatement(entry, index);
31866
+ statements.push(statement);
31867
+ }
31868
+ if (statements.length === 0) return yield* new PolicyCommandError({ message: "The document must contain at least one statement." });
31869
+ return { statements };
31870
+ });
31871
+ const validateStatement = (entry, index) => Effect.gen(function* () {
31872
+ const at = `statements[${index}]`;
31873
+ if (!isRecord(entry)) return yield* new PolicyCommandError({ message: `${at} must be a JSON object.` });
31874
+ const { effect } = entry;
31875
+ if (effect !== "allow" && effect !== "deny") return yield* new PolicyCommandError({ message: `${at}.effect must be "allow" or "deny".` });
31876
+ const { actions } = entry;
31877
+ if (!isStringArray(actions) || actions.length === 0) return yield* new PolicyCommandError({ message: `${at}.actions must be a non-empty array of action-token strings.` });
31878
+ const badAction = actions.find((token) => !isValidActionTokenShape(token));
31879
+ if (badAction !== void 0) return yield* new PolicyCommandError({ message: `${at}.actions has an invalid token "${badAction}". Use "*", "<resource>:*", or "<resource>:<action>".` });
31880
+ const { resources } = entry;
31881
+ if (!isStringArray(resources) || resources.length === 0) return yield* new PolicyCommandError({ message: `${at}.resources must be a non-empty array of selector strings.` });
31882
+ const badResource = resources.find((selector) => !isValidSelector(selector));
31883
+ if (badResource !== void 0) return yield* new PolicyCommandError({ message: `${at}.resources has an invalid selector "${badResource}". Use "*" or slash-joined segments like "project/A".` });
31884
+ const inertResource = resources.find((selector) => !isCanonicalSelector(selector));
31885
+ if (inertResource !== void 0) return yield* new PolicyCommandError({ message: `${at}.resources selector "${inertResource}" matches no known resource path. Use segments like "project/{id}/channel/{id}" or "project/{id}/env/{env}".` });
31886
+ return {
31887
+ effect,
31888
+ actions,
31889
+ resources
31890
+ };
31891
+ });
31892
+
31893
+ //#endregion
31894
+ //#region src/commands/policies/create.ts
31895
+ const DOCUMENT_HELP = "JSON document string: {\"statements\":[{\"effect\":\"allow\"|\"deny\",\"actions\":[\"<resource>:<action>\"|\"<resource>:*\"|\"*\"],\"resources\":[\"*\"|\"project/A\"|\"project/*/env/production\"]}]}";
31896
+ const createPolicyCommand = defineCommand({
31897
+ meta: {
31898
+ name: "create",
31899
+ description: "Create a named IAM policy from a JSON document"
31900
+ },
31901
+ args: {
31902
+ name: {
31903
+ type: "string",
31904
+ required: true,
31905
+ description: "Policy display name"
31906
+ },
31907
+ description: {
31908
+ type: "string",
31909
+ description: "Optional human description"
31910
+ },
31911
+ document: {
31912
+ type: "string",
31913
+ required: true,
31914
+ description: DOCUMENT_HELP
31915
+ }
31916
+ },
31917
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31918
+ const document = yield* parsePolicyDocument(args.document);
31919
+ const policy = yield* (yield* apiClient).policies.create({ payload: {
31920
+ name: args.name,
31921
+ document,
31922
+ ...compact({ description: args.description })
31923
+ } });
31924
+ yield* printHumanKeyValue([
31925
+ ["ID", policy.id],
31926
+ ["Name", policy.name],
31927
+ ["Description", policy.description ?? "-"],
31928
+ ["Statements", String(policy.document.statements.length)],
31929
+ ["Created", policy.createdAt]
31930
+ ]);
31931
+ return policy;
31932
+ }), {
31933
+ exits: policyErrorExtras,
31934
+ json: "value"
31935
+ })
31936
+ });
31937
+
31938
+ //#endregion
31939
+ //#region src/commands/policies/delete.ts
31940
+ const deletePolicyCommand = defineCommand({
31941
+ meta: {
31942
+ name: "delete",
31943
+ description: "Delete a policy and sweep its attachments (managed presets cannot be deleted)"
31944
+ },
31945
+ args: {
31946
+ id: {
31947
+ type: "positional",
31948
+ required: true,
31949
+ description: "Policy ID"
31950
+ },
31951
+ yes: {
31952
+ type: "boolean",
31953
+ description: "Skip confirmation prompt"
31954
+ }
31955
+ },
31956
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
31957
+ if (isManagedPolicyId(args.id)) return yield* new PolicyCommandError({ message: `Policy "${args.id}" is a managed preset and cannot be deleted.` });
31958
+ if (!args.yes) {
31959
+ if (!(yield* promptConfirm(`Delete policy ${args.id}?`, { initialValue: false }))) {
31960
+ yield* printHuman("Cancelled.");
31961
+ return;
31962
+ }
31963
+ }
31964
+ yield* (yield* apiClient).policies.delete({ path: { id: args.id } });
31965
+ yield* printHuman(`Deleted policy ${args.id}.`);
31966
+ return {
31967
+ id: args.id,
31968
+ deleted: true
31969
+ };
31970
+ }), {
31971
+ exits: policyErrorExtras,
31972
+ json: "value"
31973
+ })
31974
+ });
31975
+
31976
+ //#endregion
31977
+ //#region src/commands/policies/list.ts
31978
+ const listPoliciesCommand = defineCommand({
31979
+ meta: {
31980
+ name: "list",
31981
+ description: "List policies in the active organization (includes read-only managed presets)"
31982
+ },
31983
+ run: async () => runEffect(Effect.gen(function* () {
31984
+ const result = yield* (yield* apiClient).policies.list();
31985
+ yield* printHumanTable([
31986
+ "ID",
31987
+ "Name",
31988
+ "Statements",
31989
+ "Managed"
31990
+ ], result.items.map((policy) => [
31991
+ policy.id,
31992
+ policy.name,
31993
+ String(policy.document.statements.length),
31994
+ isManagedPolicyId(policy.id) ? "yes" : "no"
31995
+ ]));
31996
+ return result;
31997
+ }), {
31998
+ exits: policyErrorExtras,
31999
+ json: "value"
32000
+ })
32001
+ });
32002
+
32003
+ //#endregion
32004
+ //#region src/commands/policies/update.ts
32005
+ const updatePolicyCommand = defineCommand({
32006
+ meta: {
32007
+ name: "update",
32008
+ description: "Update a policy's name, description, or document (managed presets are read-only)"
32009
+ },
32010
+ args: {
32011
+ id: {
32012
+ type: "positional",
32013
+ required: true,
32014
+ description: "Policy ID"
32015
+ },
32016
+ name: {
32017
+ type: "string",
32018
+ description: "New display name"
32019
+ },
32020
+ description: {
32021
+ type: "string",
32022
+ description: "New description"
32023
+ },
32024
+ document: {
32025
+ type: "string",
32026
+ description: "Replacement JSON document string"
32027
+ }
32028
+ },
32029
+ run: async ({ args }) => runEffect(Effect.gen(function* () {
32030
+ if (isManagedPolicyId(args.id)) return yield* new PolicyCommandError({ message: `Policy "${args.id}" is a managed preset and is read-only. Create a custom policy instead.` });
32031
+ const document = args.document === void 0 ? void 0 : yield* parsePolicyDocument(args.document);
32032
+ const policy = yield* (yield* apiClient).policies.update({
32033
+ path: { id: args.id },
32034
+ payload: compact({
32035
+ name: args.name,
32036
+ description: args.description,
32037
+ document
32038
+ })
32039
+ });
32040
+ yield* printHumanKeyValue([
32041
+ ["ID", policy.id],
32042
+ ["Name", policy.name],
32043
+ ["Description", policy.description ?? "-"],
32044
+ ["Statements", String(policy.document.statements.length)],
32045
+ ["Updated", policy.updatedAt ?? "-"]
32046
+ ]);
32047
+ return policy;
32048
+ }), {
32049
+ exits: policyErrorExtras,
32050
+ json: "value"
32051
+ })
32052
+ });
32053
+
32054
+ //#endregion
32055
+ //#region src/commands/policies/index.ts
32056
+ const policiesCommand = defineCommand({
32057
+ meta: {
32058
+ name: "policies",
32059
+ description: "Manage IAM policies (named, reusable permission grants)"
32060
+ },
32061
+ subCommands: {
32062
+ list: listPoliciesCommand,
32063
+ create: createPolicyCommand,
32064
+ update: updatePolicyCommand,
32065
+ delete: deletePolicyCommand
32066
+ }
32067
+ });
32068
+
31571
32069
  //#endregion
31572
32070
  //#region src/commands/projects.ts
31573
- const listCommand$2 = defineCommand({
32071
+ const listCommand$1 = defineCommand({
31574
32072
  meta: {
31575
32073
  name: "list",
31576
32074
  description: "List projects (most recently active first)"
@@ -31621,7 +32119,7 @@ const listCommand$2 = defineCommand({
31621
32119
  yield* printHuman(`Page ${result.page} · ${result.items.length} of ${result.total} project(s)`);
31622
32120
  }))
31623
32121
  });
31624
- const createCommand$1 = defineCommand({
32122
+ const createCommand = defineCommand({
31625
32123
  meta: {
31626
32124
  name: "create",
31627
32125
  description: "Create a new project"
@@ -31699,7 +32197,7 @@ const renameCommand = defineCommand({
31699
32197
  return project;
31700
32198
  }), { json: "value" })
31701
32199
  });
31702
- const deleteCommand$2 = defineCommand({
32200
+ const deleteCommand$1 = defineCommand({
31703
32201
  meta: {
31704
32202
  name: "delete",
31705
32203
  description: "Delete a project"
@@ -31723,230 +32221,11 @@ const projectsCommand = defineCommand({
31723
32221
  name: "projects",
31724
32222
  description: "Manage projects"
31725
32223
  },
31726
- subCommands: {
31727
- list: listCommand$2,
31728
- create: createCommand$1,
31729
- get: getCommand,
31730
- rename: renameCommand,
31731
- delete: deleteCommand$2
31732
- }
31733
- });
31734
-
31735
- //#endregion
31736
- //#region src/commands/roles/helpers.ts
31737
- var RoleCommandError = class extends Data.TaggedError("RoleCommandError") {};
31738
- const roleErrorExtras = { RoleCommandError: 2 };
31739
- /**
31740
- * Parse comma-separated "resource:action" tokens into the PermissionGrant array
31741
- * the API expects. Multiple actions for the same resource are grouped.
31742
- *
31743
- * Input examples:
31744
- * "channel:read,channel:update"
31745
- * "channel:read, rollout:create, rollout:update"
31746
- */
31747
- const parsePermissionTokens = (raw) => Effect.gen(function* () {
31748
- const tokens = raw.split(",").map((tok) => tok.trim()).filter((tok) => tok.length > 0);
31749
- const grouped = /* @__PURE__ */ new Map();
31750
- for (const token of tokens) {
31751
- const colonIdx = token.indexOf(":");
31752
- if (colonIdx === -1) return yield* new RoleCommandError({ message: `Invalid permission token "${token}" — expected "resource:action" format.` });
31753
- const resource = token.slice(0, colonIdx).trim();
31754
- const action = token.slice(colonIdx + 1).trim();
31755
- if (!resource || !action) return yield* new RoleCommandError({ message: `Invalid permission token "${token}" — resource and action must be non-empty.` });
31756
- const actions = grouped.get(resource) ?? /* @__PURE__ */ new Set();
31757
- actions.add(action);
31758
- grouped.set(resource, actions);
31759
- }
31760
- return [...grouped.entries()].map(([resource, actions]) => ({
31761
- resource,
31762
- actions: [...actions]
31763
- }));
31764
- });
31765
-
31766
- //#endregion
31767
- //#region src/commands/roles/create.ts
31768
- const createCommand = defineCommand({
31769
- meta: {
31770
- name: "create",
31771
- description: "Create a custom role"
31772
- },
31773
- args: {
31774
- name: {
31775
- type: "string",
31776
- required: true,
31777
- description: "Role name (unique per organization)"
31778
- },
31779
- permission: {
31780
- type: "string",
31781
- required: true,
31782
- description: "Permission tokens in resource:action format, comma-separated (e.g. channel:read,channel:update)"
31783
- }
31784
- },
31785
- run: async ({ args }) => runEffect(Effect.gen(function* () {
31786
- const permissions = yield* parsePermissionTokens(args.permission);
31787
- const role = yield* (yield* apiClient).roles.create({ payload: {
31788
- name: args.name,
31789
- permissions
31790
- } });
31791
- yield* printHumanKeyValue([
31792
- ["ID", role.id],
31793
- ["Name", role.role],
31794
- ["Permissions", role.permissions.map((perm) => `${perm.resource}:[${perm.actions.join(",")}]`).join("; ")],
31795
- ["Created", role.createdAt]
31796
- ]);
31797
- return role;
31798
- }), {
31799
- exits: roleErrorExtras,
31800
- json: "value"
31801
- })
31802
- });
31803
-
31804
- //#endregion
31805
- //#region src/commands/roles/delete.ts
31806
- const deleteCommand$1 = defineCommand({
31807
- meta: {
31808
- name: "delete",
31809
- description: "Delete a custom role"
31810
- },
31811
- args: {
31812
- id: {
31813
- type: "positional",
31814
- required: true,
31815
- description: "Role ID"
31816
- },
31817
- yes: {
31818
- type: "boolean",
31819
- description: "Skip confirmation prompt"
31820
- }
31821
- },
31822
- run: async ({ args }) => runEffect(Effect.gen(function* () {
31823
- if (!args.yes) {
31824
- if (!(yield* promptConfirm(`Delete role ${args.id}?`, { initialValue: false }))) {
31825
- yield* printHuman("Cancelled.");
31826
- return { deleted: 0 };
31827
- }
31828
- }
31829
- const result = yield* (yield* apiClient).roles.delete({ path: { id: args.id } });
31830
- yield* printHuman(`Role ${args.id} deleted.`);
31831
- return result;
31832
- }), {
31833
- exits: roleErrorExtras,
31834
- json: "value"
31835
- })
31836
- });
31837
-
31838
- //#endregion
31839
- //#region src/commands/roles/list.ts
31840
- const listCommand$1 = defineCommand({
31841
- meta: {
31842
- name: "list",
31843
- description: "List custom roles for the active organization"
31844
- },
31845
- args: { "organization-id": {
31846
- type: "string",
31847
- required: true,
31848
- description: "Organization ID to list roles for"
31849
- } },
31850
- run: async ({ args }) => runEffect(Effect.gen(function* () {
31851
- yield* printList([
31852
- "ID",
31853
- "Name",
31854
- "Permissions",
31855
- "Created"
31856
- ], (yield* (yield* apiClient).roles.list({ urlParams: { organizationId: args["organization-id"] } })).map((role) => [
31857
- role.id,
31858
- role.role,
31859
- role.permissions.map((perm) => `${perm.resource}:[${perm.actions.join(",")}]`).join("; "),
31860
- role.createdAt
31861
- ]), "No custom roles found.");
31862
- }), roleErrorExtras)
31863
- });
31864
-
31865
- //#endregion
31866
- //#region src/commands/roles/update.ts
31867
- const updateCommand$1 = defineCommand({
31868
- meta: {
31869
- name: "update",
31870
- description: "Update a custom role's name or permissions"
31871
- },
31872
- args: {
31873
- id: {
31874
- type: "positional",
31875
- required: true,
31876
- description: "Role ID"
31877
- },
31878
- name: {
31879
- type: "string",
31880
- description: "New role name"
31881
- },
31882
- permission: {
31883
- type: "string",
31884
- description: "Replacement permission tokens in resource:action format, comma-separated (e.g. channel:read,channel:update)"
31885
- }
31886
- },
31887
- run: async ({ args }) => runEffect(Effect.gen(function* () {
31888
- const permissions = args.permission === void 0 ? void 0 : yield* parsePermissionTokens(args.permission);
31889
- const role = yield* (yield* apiClient).roles.update({
31890
- path: { id: args.id },
31891
- payload: compact({
31892
- name: args.name,
31893
- permissions
31894
- })
31895
- });
31896
- yield* printHumanKeyValue([
31897
- ["ID", role.id],
31898
- ["Name", role.role],
31899
- ["Permissions", role.permissions.map((perm) => `${perm.resource}:[${perm.actions.join(",")}]`).join("; ")],
31900
- ["Updated", role.updatedAt ?? "-"]
31901
- ]);
31902
- return role;
31903
- }), {
31904
- exits: roleErrorExtras,
31905
- json: "value"
31906
- })
31907
- });
31908
-
31909
- //#endregion
31910
- //#region src/commands/roles/view.ts
31911
- const viewCommand$1 = defineCommand({
31912
- meta: {
31913
- name: "view",
31914
- description: "Show a custom role by ID"
31915
- },
31916
- args: { id: {
31917
- type: "positional",
31918
- required: true,
31919
- description: "Role ID"
31920
- } },
31921
- run: async ({ args }) => runEffect(Effect.gen(function* () {
31922
- const role = yield* (yield* apiClient).roles.get({ path: { id: args.id } });
31923
- yield* printHumanKeyValue([
31924
- ["ID", role.id],
31925
- ["Name", role.role],
31926
- ["Organization ID", role.organizationId],
31927
- ["Permissions", role.permissions.map((perm) => `${perm.resource}:[${perm.actions.join(",")}]`).join("; ")],
31928
- ["Created", role.createdAt],
31929
- ["Updated", role.updatedAt ?? "-"]
31930
- ]);
31931
- return role;
31932
- }), {
31933
- exits: roleErrorExtras,
31934
- json: "value"
31935
- })
31936
- });
31937
-
31938
- //#endregion
31939
- //#region src/commands/roles/index.ts
31940
- const rolesCommand = defineCommand({
31941
- meta: {
31942
- name: "roles",
31943
- description: "Manage custom organization roles"
31944
- },
31945
32224
  subCommands: {
31946
32225
  list: listCommand$1,
31947
- view: viewCommand$1,
31948
32226
  create: createCommand,
31949
- update: updateCommand$1,
32227
+ get: getCommand,
32228
+ rename: renameCommand,
31950
32229
  delete: deleteCommand$1
31951
32230
  }
31952
32231
  });
@@ -33423,10 +33702,10 @@ const assertSignedManifestBundleUrl = (params) => Effect.gen(function* () {
33423
33702
  try: () => JSON.parse(params.manifestBody),
33424
33703
  catch: () => params.makeError(`Signed ${params.platform} manifestBody is not valid JSON.`)
33425
33704
  });
33426
- if (!isRecord(parsed)) return yield* Effect.fail(params.makeError(`Signed ${params.platform} manifestBody must be a JSON object.`));
33705
+ if (!isRecord$1(parsed)) return yield* Effect.fail(params.makeError(`Signed ${params.platform} manifestBody must be a JSON object.`));
33427
33706
  const { id } = parsed;
33428
33707
  const { launchAsset } = parsed;
33429
- if (typeof id !== "string" || !isRecord(launchAsset)) return yield* Effect.fail(params.makeError(`Signed ${params.platform} manifestBody must carry a string "id" and an object "launchAsset".`));
33708
+ if (typeof id !== "string" || !isRecord$1(launchAsset)) return yield* Effect.fail(params.makeError(`Signed ${params.platform} manifestBody must carry a string "id" and an object "launchAsset".`));
33430
33709
  const { url } = launchAsset;
33431
33710
  const expectedPrefix = `${params.serverBaseUrl}/manifest/${params.projectId}/bundle/${id}/`;
33432
33711
  if (typeof url !== "string" || !url.startsWith(expectedPrefix)) return yield* Effect.fail(params.makeError(`Signed ${params.platform} manifestBody launchAsset.url must point at the Worker bundle route (${expectedPrefix}…) so bsdiff patches apply; got ${typeof url === "string" ? url : "a non-string value"}. Re-render the signed manifest with the Worker bundle URL.`));
@@ -34354,10 +34633,10 @@ const extractDirectiveCommitTime = (directiveBody) => Effect.gen(function* () {
34354
34633
  try: () => JSON.parse(directiveBody),
34355
34634
  catch: () => new UpdateRollbackError({ message: "directiveBody must be valid JSON." })
34356
34635
  });
34357
- if (!isRecord(directive)) return yield* new UpdateRollbackError({ message: "directiveBody must decode to a JSON object." });
34636
+ if (!isRecord$1(directive)) return yield* new UpdateRollbackError({ message: "directiveBody must decode to a JSON object." });
34358
34637
  if (directive["type"] !== "rollBackToEmbedded") return yield* new UpdateRollbackError({ message: "directiveBody.type must be \"rollBackToEmbedded\"." });
34359
34638
  const { parameters } = directive;
34360
- if (!isRecord(parameters)) return yield* new UpdateRollbackError({ message: "directiveBody.parameters must be an object." });
34639
+ if (!isRecord$1(parameters)) return yield* new UpdateRollbackError({ message: "directiveBody.parameters must be an object." });
34361
34640
  const { commitTime } = parameters;
34362
34641
  if (typeof commitTime !== "string" || Number.isNaN(Date.parse(commitTime))) return yield* new UpdateRollbackError({ message: "directiveBody.parameters.commitTime must be a valid ISO 8601 timestamp." });
34363
34642
  if (new Date(Date.parse(commitTime)).toISOString() !== commitTime) return yield* new UpdateRollbackError({ message: "directiveBody.parameters.commitTime must be canonical ISO 8601 with milliseconds and a trailing Z (e.g. 2026-05-06T14:00:00.000Z). The expo-updates client cannot parse other forms (no milliseconds, numeric timezone offsets), so the signed rollback would be rejected on-device." });
@@ -35185,9 +35464,10 @@ const commandRegistry = {
35185
35464
  init: initCommand,
35186
35465
  status: statusCommand,
35187
35466
  projects: projectsCommand,
35467
+ policies: policiesCommand,
35468
+ groups: groupsCommand,
35188
35469
  branches: branchesCommand,
35189
35470
  channels: channelsCommand,
35190
- roles: rolesCommand,
35191
35471
  build: buildCommand,
35192
35472
  builds: buildsCommand,
35193
35473
  credentials: credentialsCommand,