@viyv/account-client 0.2.0 → 0.3.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.
@@ -5,6 +5,7 @@ function parseEntitlement(wire) {
5
5
  plan: wire.plan,
6
6
  addons: wire.addons ?? [],
7
7
  products: wire.products ?? [],
8
+ productPlans: wire.product_plans ?? {},
8
9
  features: wire.features
9
10
  };
10
11
  }
@@ -12,12 +13,14 @@ function entitlementForProduct(entitlement, product) {
12
13
  if (!entitlement) return null;
13
14
  return {
14
15
  enabled: entitlement.products.includes(product),
15
- plan: entitlement.plan,
16
+ // Per-product billing: report THIS product's own tier (free-start default),
17
+ // not the org-wide effective `plan`.
18
+ plan: entitlement.productPlans[product] ?? "free",
16
19
  addons: entitlement.addons,
17
20
  features: entitlement.features
18
21
  };
19
22
  }
20
23
 
21
24
  export { entitlementForProduct, parseEntitlement };
22
- //# sourceMappingURL=chunk-UX6UAFIF.js.map
23
- //# sourceMappingURL=chunk-UX6UAFIF.js.map
25
+ //# sourceMappingURL=chunk-V3SLFECG.js.map
26
+ //# sourceMappingURL=chunk-V3SLFECG.js.map
@@ -16,16 +16,24 @@ interface AccountOrg {
16
16
  /** Shape of GET /v1/license/entitlement (docs/09 A-4). */
17
17
  interface Entitlement {
18
18
  organizationId: string;
19
+ /**
20
+ * The org's effective (highest) tier across its products — a back-compat
21
+ * summary. Per-product billing (2026-06-13): the authoritative per-product
22
+ * tier is in {@link Entitlement.productPlans}.
23
+ */
19
24
  plan: PlanTier;
20
25
  addons: string[];
21
26
  /** Product slugs this org may use (free-start lists the agent family). */
22
27
  products: ProductSlug[];
28
+ /** Per-product tier (each enabled product → its plan; free-start = "free"). */
29
+ productPlans: Partial<Record<ProductSlug, PlanTier>>;
23
30
  features: EntitlementFeatures;
24
31
  }
25
32
  /** Per-product entitlement view returned by useEntitlement(slug). */
26
33
  interface ProductEntitlement {
27
34
  /** Whether the product is enabled for this org. */
28
35
  enabled: boolean;
36
+ /** THIS product's tier (per-product billing) — not the org-wide summary. */
29
37
  plan: PlanTier;
30
38
  addons: string[];
31
39
  /** The full feature map (callers branch on the flags they care about). */
@@ -37,6 +45,7 @@ interface EntitlementWire {
37
45
  plan: PlanTier;
38
46
  addons: string[];
39
47
  products: ProductSlug[];
48
+ product_plans?: Partial<Record<ProductSlug, PlanTier>>;
40
49
  features: EntitlementFeatures;
41
50
  }
42
51
  /** Normalize the wire payload into the camelCase {@link Entitlement}. */
@@ -16,16 +16,24 @@ interface AccountOrg {
16
16
  /** Shape of GET /v1/license/entitlement (docs/09 A-4). */
17
17
  interface Entitlement {
18
18
  organizationId: string;
19
+ /**
20
+ * The org's effective (highest) tier across its products — a back-compat
21
+ * summary. Per-product billing (2026-06-13): the authoritative per-product
22
+ * tier is in {@link Entitlement.productPlans}.
23
+ */
19
24
  plan: PlanTier;
20
25
  addons: string[];
21
26
  /** Product slugs this org may use (free-start lists the agent family). */
22
27
  products: ProductSlug[];
28
+ /** Per-product tier (each enabled product → its plan; free-start = "free"). */
29
+ productPlans: Partial<Record<ProductSlug, PlanTier>>;
23
30
  features: EntitlementFeatures;
24
31
  }
25
32
  /** Per-product entitlement view returned by useEntitlement(slug). */
26
33
  interface ProductEntitlement {
27
34
  /** Whether the product is enabled for this org. */
28
35
  enabled: boolean;
36
+ /** THIS product's tier (per-product billing) — not the org-wide summary. */
29
37
  plan: PlanTier;
30
38
  addons: string[];
31
39
  /** The full feature map (callers branch on the flags they care about). */
@@ -37,6 +45,7 @@ interface EntitlementWire {
37
45
  plan: PlanTier;
38
46
  addons: string[];
39
47
  products: ProductSlug[];
48
+ product_plans?: Partial<Record<ProductSlug, PlanTier>>;
40
49
  features: EntitlementFeatures;
41
50
  }
42
51
  /** Normalize the wire payload into the camelCase {@link Entitlement}. */
package/dist/index.cjs CHANGED
@@ -69,6 +69,7 @@ function parseEntitlement(wire) {
69
69
  plan: wire.plan,
70
70
  addons: wire.addons ?? [],
71
71
  products: wire.products ?? [],
72
+ productPlans: wire.product_plans ?? {},
72
73
  features: wire.features
73
74
  };
74
75
  }
@@ -76,7 +77,9 @@ function entitlementForProduct(entitlement, product) {
76
77
  if (!entitlement) return null;
77
78
  return {
78
79
  enabled: entitlement.products.includes(product),
79
- plan: entitlement.plan,
80
+ // Per-product billing: report THIS product's own tier (free-start default),
81
+ // not the org-wide effective `plan`.
82
+ plan: entitlement.productPlans[product] ?? "free",
80
83
  addons: entitlement.addons,
81
84
  features: entitlement.features
82
85
  };
@@ -101,6 +104,21 @@ function createAccountClient(config) {
101
104
  email: params.email,
102
105
  password: params.password
103
106
  }),
107
+ signInWithProvider: (provider, options) => client$1.signIn.social({
108
+ provider,
109
+ callbackURL: options.callbackURL,
110
+ errorCallbackURL: options.errorCallbackURL
111
+ }),
112
+ listAccounts: () => client$1.listAccounts(),
113
+ linkSocial: (provider, options) => client$1.linkSocial({
114
+ provider,
115
+ callbackURL: options.callbackURL,
116
+ errorCallbackURL: options.errorCallbackURL
117
+ }),
118
+ unlinkAccount: (params) => client$1.unlinkAccount({
119
+ providerId: params.providerId,
120
+ accountId: params.accountId
121
+ }),
104
122
  signOut: () => client$1.signOut(),
105
123
  getSession: () => client$1.getSession()
106
124
  };
package/dist/index.d.cts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createAuthClient } from 'better-auth/client';
2
2
  import { DeviceUserinfoResponse } from '@viyv/shared';
3
3
  export { DeviceUserinfoResponse, EntitlementFeatures, PlanTier, ProductSlug } from '@viyv/shared';
4
- export { A as AccountOrg, a as AccountUser, E as Entitlement, P as ProductEntitlement, e as entitlementForProduct, p as parseEntitlement } from './entitlement-B3pXM2vO.cjs';
4
+ export { A as AccountOrg, a as AccountUser, E as Entitlement, P as ProductEntitlement, e as entitlementForProduct, p as parseEntitlement } from './entitlement-CfKncUyH.cjs';
5
5
 
6
6
  declare const DEFAULT_DEVICE_CLIENT_ID: "viyv-browser-macapp";
7
7
  /** RFC 8628 §3.4 device_code grant type (token endpoint `grant_type`). */
@@ -85,6 +85,29 @@ interface AuthResult<T = unknown> {
85
85
  statusText?: string;
86
86
  } | null;
87
87
  }
88
+ /** OAuth providers exposed on the hosted signin surface (app.viyv.io). */
89
+ type SocialProvider = "google" | "github";
90
+ interface SocialRedirectOptions {
91
+ /**
92
+ * Where the provider returns the user AFTER auth completes (success). Social
93
+ * login is a FULL-PAGE redirect, so this is the ONLY carrier that survives the
94
+ * provider round-trip — unlike the email/password path, you cannot set
95
+ * window.location after the call. Pass an open-redirect-validated target (the
96
+ * hosted signin runs `safeReturnTo()` before handing the value here).
97
+ */
98
+ callbackURL: string;
99
+ /** Where to send the user if the OAuth flow errors (e.g. "/signin?error=oauth"). */
100
+ errorCallbackURL?: string;
101
+ }
102
+ /** One linked credential/OAuth account, as returned by GET /list-accounts. */
103
+ interface LinkedAccount {
104
+ id: string;
105
+ providerId: string;
106
+ accountId: string;
107
+ scopes?: string[];
108
+ createdAt?: string;
109
+ updatedAt?: string;
110
+ }
88
111
  /**
89
112
  * Public surface of the account client. Methods are typed explicitly (rather
90
113
  * than inferred) so the published `.d.ts` does not reference better-auth's
@@ -102,6 +125,28 @@ interface AccountClient {
102
125
  email: string;
103
126
  password: string;
104
127
  }): Promise<AuthResult>;
128
+ /**
129
+ * Begin an OAuth login. NAVIGATES the browser to the provider (full-page
130
+ * redirect), so on success it does not resolve to a useful value — control
131
+ * leaves the page. `options.callbackURL` chooses where the user lands after
132
+ * the round-trip; the AuthResult is meaningful only for a synchronous error.
133
+ */
134
+ signInWithProvider(provider: SocialProvider, options: SocialRedirectOptions): Promise<AuthResult>;
135
+ /** List the signed-in user's linked accounts (credential + OAuth). */
136
+ listAccounts(): Promise<AuthResult<LinkedAccount[]>>;
137
+ /**
138
+ * Link an OAuth provider to the SIGNED-IN user (explicit link, not implicit).
139
+ * Also a full-page redirect to the provider.
140
+ */
141
+ linkSocial(provider: SocialProvider, options: SocialRedirectOptions): Promise<AuthResult>;
142
+ /**
143
+ * Unlink an OAuth/credential account from the signed-in user. Fails server-side
144
+ * if it would remove the user's last remaining credential.
145
+ */
146
+ unlinkAccount(params: {
147
+ providerId: string;
148
+ accountId?: string;
149
+ }): Promise<AuthResult>;
105
150
  signOut(): Promise<AuthResult>;
106
151
  getSession(): Promise<AuthResult>;
107
152
  }
@@ -112,4 +157,4 @@ interface AccountClient {
112
157
  */
113
158
  declare function createAccountClient(config: AccountClientConfig): AccountClient;
114
159
 
115
- export { ACCOUNT_CLIENT_PACKAGE, type AccountClient, type AccountClientConfig, type AuthResult, DEFAULT_DEVICE_CLIENT_ID, DEVICE_CODE_GRANT_TYPE, type DeviceFlowClient, type DeviceFlowClientConfig, type DevicePollError, type DeviceStartResponse, type DeviceTokenResponse, type LicenseCurrentResponse, createAccountClient, createDeviceFlowClient };
160
+ export { ACCOUNT_CLIENT_PACKAGE, type AccountClient, type AccountClientConfig, type AuthResult, DEFAULT_DEVICE_CLIENT_ID, DEVICE_CODE_GRANT_TYPE, type DeviceFlowClient, type DeviceFlowClientConfig, type DevicePollError, type DeviceStartResponse, type DeviceTokenResponse, type LicenseCurrentResponse, type LinkedAccount, type SocialProvider, type SocialRedirectOptions, createAccountClient, createDeviceFlowClient };
package/dist/index.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { createAuthClient } from 'better-auth/client';
2
2
  import { DeviceUserinfoResponse } from '@viyv/shared';
3
3
  export { DeviceUserinfoResponse, EntitlementFeatures, PlanTier, ProductSlug } from '@viyv/shared';
4
- export { A as AccountOrg, a as AccountUser, E as Entitlement, P as ProductEntitlement, e as entitlementForProduct, p as parseEntitlement } from './entitlement-B3pXM2vO.js';
4
+ export { A as AccountOrg, a as AccountUser, E as Entitlement, P as ProductEntitlement, e as entitlementForProduct, p as parseEntitlement } from './entitlement-CfKncUyH.js';
5
5
 
6
6
  declare const DEFAULT_DEVICE_CLIENT_ID: "viyv-browser-macapp";
7
7
  /** RFC 8628 §3.4 device_code grant type (token endpoint `grant_type`). */
@@ -85,6 +85,29 @@ interface AuthResult<T = unknown> {
85
85
  statusText?: string;
86
86
  } | null;
87
87
  }
88
+ /** OAuth providers exposed on the hosted signin surface (app.viyv.io). */
89
+ type SocialProvider = "google" | "github";
90
+ interface SocialRedirectOptions {
91
+ /**
92
+ * Where the provider returns the user AFTER auth completes (success). Social
93
+ * login is a FULL-PAGE redirect, so this is the ONLY carrier that survives the
94
+ * provider round-trip — unlike the email/password path, you cannot set
95
+ * window.location after the call. Pass an open-redirect-validated target (the
96
+ * hosted signin runs `safeReturnTo()` before handing the value here).
97
+ */
98
+ callbackURL: string;
99
+ /** Where to send the user if the OAuth flow errors (e.g. "/signin?error=oauth"). */
100
+ errorCallbackURL?: string;
101
+ }
102
+ /** One linked credential/OAuth account, as returned by GET /list-accounts. */
103
+ interface LinkedAccount {
104
+ id: string;
105
+ providerId: string;
106
+ accountId: string;
107
+ scopes?: string[];
108
+ createdAt?: string;
109
+ updatedAt?: string;
110
+ }
88
111
  /**
89
112
  * Public surface of the account client. Methods are typed explicitly (rather
90
113
  * than inferred) so the published `.d.ts` does not reference better-auth's
@@ -102,6 +125,28 @@ interface AccountClient {
102
125
  email: string;
103
126
  password: string;
104
127
  }): Promise<AuthResult>;
128
+ /**
129
+ * Begin an OAuth login. NAVIGATES the browser to the provider (full-page
130
+ * redirect), so on success it does not resolve to a useful value — control
131
+ * leaves the page. `options.callbackURL` chooses where the user lands after
132
+ * the round-trip; the AuthResult is meaningful only for a synchronous error.
133
+ */
134
+ signInWithProvider(provider: SocialProvider, options: SocialRedirectOptions): Promise<AuthResult>;
135
+ /** List the signed-in user's linked accounts (credential + OAuth). */
136
+ listAccounts(): Promise<AuthResult<LinkedAccount[]>>;
137
+ /**
138
+ * Link an OAuth provider to the SIGNED-IN user (explicit link, not implicit).
139
+ * Also a full-page redirect to the provider.
140
+ */
141
+ linkSocial(provider: SocialProvider, options: SocialRedirectOptions): Promise<AuthResult>;
142
+ /**
143
+ * Unlink an OAuth/credential account from the signed-in user. Fails server-side
144
+ * if it would remove the user's last remaining credential.
145
+ */
146
+ unlinkAccount(params: {
147
+ providerId: string;
148
+ accountId?: string;
149
+ }): Promise<AuthResult>;
105
150
  signOut(): Promise<AuthResult>;
106
151
  getSession(): Promise<AuthResult>;
107
152
  }
@@ -112,4 +157,4 @@ interface AccountClient {
112
157
  */
113
158
  declare function createAccountClient(config: AccountClientConfig): AccountClient;
114
159
 
115
- export { ACCOUNT_CLIENT_PACKAGE, type AccountClient, type AccountClientConfig, type AuthResult, DEFAULT_DEVICE_CLIENT_ID, DEVICE_CODE_GRANT_TYPE, type DeviceFlowClient, type DeviceFlowClientConfig, type DevicePollError, type DeviceStartResponse, type DeviceTokenResponse, type LicenseCurrentResponse, createAccountClient, createDeviceFlowClient };
160
+ export { ACCOUNT_CLIENT_PACKAGE, type AccountClient, type AccountClientConfig, type AuthResult, DEFAULT_DEVICE_CLIENT_ID, DEVICE_CODE_GRANT_TYPE, type DeviceFlowClient, type DeviceFlowClientConfig, type DevicePollError, type DeviceStartResponse, type DeviceTokenResponse, type LicenseCurrentResponse, type LinkedAccount, type SocialProvider, type SocialRedirectOptions, createAccountClient, createDeviceFlowClient };
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
- export { entitlementForProduct, parseEntitlement } from './chunk-UX6UAFIF.js';
1
+ export { entitlementForProduct, parseEntitlement } from './chunk-V3SLFECG.js';
2
2
  import { createAuthClient } from 'better-auth/client';
3
3
 
4
4
  // src/device.ts
@@ -78,6 +78,21 @@ function createAccountClient(config) {
78
78
  email: params.email,
79
79
  password: params.password
80
80
  }),
81
+ signInWithProvider: (provider, options) => client.signIn.social({
82
+ provider,
83
+ callbackURL: options.callbackURL,
84
+ errorCallbackURL: options.errorCallbackURL
85
+ }),
86
+ listAccounts: () => client.listAccounts(),
87
+ linkSocial: (provider, options) => client.linkSocial({
88
+ provider,
89
+ callbackURL: options.callbackURL,
90
+ errorCallbackURL: options.errorCallbackURL
91
+ }),
92
+ unlinkAccount: (params) => client.unlinkAccount({
93
+ providerId: params.providerId,
94
+ accountId: params.accountId
95
+ }),
81
96
  signOut: () => client.signOut(),
82
97
  getSession: () => client.getSession()
83
98
  };
@@ -7,6 +7,7 @@ function parseEntitlement(wire) {
7
7
  plan: wire.plan,
8
8
  addons: wire.addons ?? [],
9
9
  products: wire.products ?? [],
10
+ productPlans: wire.product_plans ?? {},
10
11
  features: wire.features
11
12
  };
12
13
  }
@@ -59,7 +60,10 @@ function createIntrospectionClient(config) {
59
60
  try {
60
61
  raw = await res.json();
61
62
  } catch (err) {
62
- return { status: "integrity_error", reason: `introspect unparseable body: ${errMessage(err)}` };
63
+ return {
64
+ status: "integrity_error",
65
+ reason: `introspect unparseable body: ${errMessage(err)}`
66
+ };
63
67
  }
64
68
  if (!raw || typeof raw !== "object") {
65
69
  return { status: "integrity_error", reason: "introspect non-object body" };
@@ -105,6 +109,65 @@ function createIntrospectionClient(config) {
105
109
  } catch (err) {
106
110
  return { status: "unavailable", reason: `entitlement unparseable body: ${errMessage(err)}` };
107
111
  }
112
+ },
113
+ async listOrgMembers(orgId) {
114
+ let res;
115
+ try {
116
+ res = await call(`/v1/orgs/${encodeURIComponent(orgId)}/members`, {
117
+ method: "GET",
118
+ headers: secretHeaders
119
+ });
120
+ } catch (err) {
121
+ return { status: "unavailable", reason: `roster transport error: ${errMessage(err)}` };
122
+ }
123
+ if (!res.ok) return { status: "unavailable", reason: `roster http ${res.status}` };
124
+ try {
125
+ const body = await res.json();
126
+ return {
127
+ status: "ok",
128
+ members: (body.members ?? []).map((m) => ({
129
+ userId: m.user_id,
130
+ email: m.email ?? null,
131
+ name: m.name ?? null,
132
+ role: m.role,
133
+ seatNo: m.seat_no ?? null
134
+ }))
135
+ };
136
+ } catch (err) {
137
+ return { status: "unavailable", reason: `roster unparseable body: ${errMessage(err)}` };
138
+ }
139
+ },
140
+ async listOrgTeams(orgId) {
141
+ let res;
142
+ try {
143
+ res = await call(`/v1/orgs/${encodeURIComponent(orgId)}/teams`, {
144
+ method: "GET",
145
+ headers: secretHeaders
146
+ });
147
+ } catch (err) {
148
+ return { status: "unavailable", reason: `teams transport error: ${errMessage(err)}` };
149
+ }
150
+ if (!res.ok) return { status: "unavailable", reason: `teams http ${res.status}` };
151
+ try {
152
+ const body = await res.json();
153
+ return {
154
+ status: "ok",
155
+ teams: (body.teams ?? []).map((t) => ({
156
+ id: t.id,
157
+ slug: t.slug,
158
+ name: t.name,
159
+ members: (t.members ?? []).map((m) => ({
160
+ userId: m.user_id,
161
+ orgMemberId: m.org_member_id,
162
+ teamRole: m.team_role,
163
+ email: m.email ?? null,
164
+ name: m.name ?? null
165
+ }))
166
+ }))
167
+ };
168
+ } catch (err) {
169
+ return { status: "unavailable", reason: `teams unparseable body: ${errMessage(err)}` };
170
+ }
108
171
  }
109
172
  };
110
173
  }
@@ -1,4 +1,4 @@
1
- import { E as Entitlement } from './entitlement-B3pXM2vO.cjs';
1
+ import { E as Entitlement } from './entitlement-CfKncUyH.cjs';
2
2
  import '@viyv/shared';
3
3
 
4
4
  interface IntrospectionClientConfig {
@@ -56,12 +56,54 @@ type OrgEntitlementOutcome = {
56
56
  status: "unavailable";
57
57
  reason: string;
58
58
  };
59
+ /** An org member resolved from the m2m roster read (camelCase). */
60
+ interface OrgMemberInfo {
61
+ userId: string;
62
+ email: string | null;
63
+ name: string | null;
64
+ role: string;
65
+ seatNo: number | null;
66
+ }
67
+ /** A sub-team member resolved from the m2m teams read. */
68
+ interface OrgTeamMemberInfo {
69
+ userId: string;
70
+ orgMemberId: string;
71
+ /** lead | member (free-text at the source; the caller maps it). */
72
+ teamRole: string;
73
+ email: string | null;
74
+ name: string | null;
75
+ }
76
+ /** A sub-team resolved from the m2m teams read. */
77
+ interface OrgTeamInfo {
78
+ id: string;
79
+ slug: string;
80
+ name: string;
81
+ members: OrgTeamMemberInfo[];
82
+ }
83
+ type OrgRosterOutcome = {
84
+ status: "ok";
85
+ members: OrgMemberInfo[];
86
+ } | {
87
+ status: "unavailable";
88
+ reason: string;
89
+ };
90
+ type OrgTeamsOutcome = {
91
+ status: "ok";
92
+ teams: OrgTeamInfo[];
93
+ } | {
94
+ status: "unavailable";
95
+ reason: string;
96
+ };
59
97
  interface IntrospectionClient {
60
98
  /** Resolve a device access token to a subject (fail-closed, see taxonomy). */
61
99
  introspectDevice(accessToken: string): Promise<IntrospectionOutcome>;
62
100
  /** Read an org's entitlement by id (m2m). */
63
101
  getOrgEntitlement(orgId: string): Promise<OrgEntitlementOutcome>;
102
+ /** Read an org's member roster (m2m, service secret). Names are render-time only. */
103
+ listOrgMembers(orgId: string): Promise<OrgRosterOutcome>;
104
+ /** Read an org's sub-teams + their members (m2m, service secret). */
105
+ listOrgTeams(orgId: string): Promise<OrgTeamsOutcome>;
64
106
  }
65
107
  declare function createIntrospectionClient(config: IntrospectionClientConfig): IntrospectionClient;
66
108
 
67
- export { type IntrospectionClient, type IntrospectionClientConfig, type IntrospectionOutcome, type OrgEntitlementOutcome, type ResolvedDeviceSubject, createIntrospectionClient };
109
+ export { type IntrospectionClient, type IntrospectionClientConfig, type IntrospectionOutcome, type OrgEntitlementOutcome, type OrgMemberInfo, type OrgRosterOutcome, type OrgTeamInfo, type OrgTeamMemberInfo, type OrgTeamsOutcome, type ResolvedDeviceSubject, createIntrospectionClient };
@@ -1,4 +1,4 @@
1
- import { E as Entitlement } from './entitlement-B3pXM2vO.js';
1
+ import { E as Entitlement } from './entitlement-CfKncUyH.js';
2
2
  import '@viyv/shared';
3
3
 
4
4
  interface IntrospectionClientConfig {
@@ -56,12 +56,54 @@ type OrgEntitlementOutcome = {
56
56
  status: "unavailable";
57
57
  reason: string;
58
58
  };
59
+ /** An org member resolved from the m2m roster read (camelCase). */
60
+ interface OrgMemberInfo {
61
+ userId: string;
62
+ email: string | null;
63
+ name: string | null;
64
+ role: string;
65
+ seatNo: number | null;
66
+ }
67
+ /** A sub-team member resolved from the m2m teams read. */
68
+ interface OrgTeamMemberInfo {
69
+ userId: string;
70
+ orgMemberId: string;
71
+ /** lead | member (free-text at the source; the caller maps it). */
72
+ teamRole: string;
73
+ email: string | null;
74
+ name: string | null;
75
+ }
76
+ /** A sub-team resolved from the m2m teams read. */
77
+ interface OrgTeamInfo {
78
+ id: string;
79
+ slug: string;
80
+ name: string;
81
+ members: OrgTeamMemberInfo[];
82
+ }
83
+ type OrgRosterOutcome = {
84
+ status: "ok";
85
+ members: OrgMemberInfo[];
86
+ } | {
87
+ status: "unavailable";
88
+ reason: string;
89
+ };
90
+ type OrgTeamsOutcome = {
91
+ status: "ok";
92
+ teams: OrgTeamInfo[];
93
+ } | {
94
+ status: "unavailable";
95
+ reason: string;
96
+ };
59
97
  interface IntrospectionClient {
60
98
  /** Resolve a device access token to a subject (fail-closed, see taxonomy). */
61
99
  introspectDevice(accessToken: string): Promise<IntrospectionOutcome>;
62
100
  /** Read an org's entitlement by id (m2m). */
63
101
  getOrgEntitlement(orgId: string): Promise<OrgEntitlementOutcome>;
102
+ /** Read an org's member roster (m2m, service secret). Names are render-time only. */
103
+ listOrgMembers(orgId: string): Promise<OrgRosterOutcome>;
104
+ /** Read an org's sub-teams + their members (m2m, service secret). */
105
+ listOrgTeams(orgId: string): Promise<OrgTeamsOutcome>;
64
106
  }
65
107
  declare function createIntrospectionClient(config: IntrospectionClientConfig): IntrospectionClient;
66
108
 
67
- export { type IntrospectionClient, type IntrospectionClientConfig, type IntrospectionOutcome, type OrgEntitlementOutcome, type ResolvedDeviceSubject, createIntrospectionClient };
109
+ export { type IntrospectionClient, type IntrospectionClientConfig, type IntrospectionOutcome, type OrgEntitlementOutcome, type OrgMemberInfo, type OrgRosterOutcome, type OrgTeamInfo, type OrgTeamMemberInfo, type OrgTeamsOutcome, type ResolvedDeviceSubject, createIntrospectionClient };
@@ -1,4 +1,4 @@
1
- import { parseEntitlement } from './chunk-UX6UAFIF.js';
1
+ import { parseEntitlement } from './chunk-V3SLFECG.js';
2
2
 
3
3
  // src/introspect.ts
4
4
  var DEFAULT_TIMEOUT_MS = 5e3;
@@ -48,7 +48,10 @@ function createIntrospectionClient(config) {
48
48
  try {
49
49
  raw = await res.json();
50
50
  } catch (err) {
51
- return { status: "integrity_error", reason: `introspect unparseable body: ${errMessage(err)}` };
51
+ return {
52
+ status: "integrity_error",
53
+ reason: `introspect unparseable body: ${errMessage(err)}`
54
+ };
52
55
  }
53
56
  if (!raw || typeof raw !== "object") {
54
57
  return { status: "integrity_error", reason: "introspect non-object body" };
@@ -94,6 +97,65 @@ function createIntrospectionClient(config) {
94
97
  } catch (err) {
95
98
  return { status: "unavailable", reason: `entitlement unparseable body: ${errMessage(err)}` };
96
99
  }
100
+ },
101
+ async listOrgMembers(orgId) {
102
+ let res;
103
+ try {
104
+ res = await call(`/v1/orgs/${encodeURIComponent(orgId)}/members`, {
105
+ method: "GET",
106
+ headers: secretHeaders
107
+ });
108
+ } catch (err) {
109
+ return { status: "unavailable", reason: `roster transport error: ${errMessage(err)}` };
110
+ }
111
+ if (!res.ok) return { status: "unavailable", reason: `roster http ${res.status}` };
112
+ try {
113
+ const body = await res.json();
114
+ return {
115
+ status: "ok",
116
+ members: (body.members ?? []).map((m) => ({
117
+ userId: m.user_id,
118
+ email: m.email ?? null,
119
+ name: m.name ?? null,
120
+ role: m.role,
121
+ seatNo: m.seat_no ?? null
122
+ }))
123
+ };
124
+ } catch (err) {
125
+ return { status: "unavailable", reason: `roster unparseable body: ${errMessage(err)}` };
126
+ }
127
+ },
128
+ async listOrgTeams(orgId) {
129
+ let res;
130
+ try {
131
+ res = await call(`/v1/orgs/${encodeURIComponent(orgId)}/teams`, {
132
+ method: "GET",
133
+ headers: secretHeaders
134
+ });
135
+ } catch (err) {
136
+ return { status: "unavailable", reason: `teams transport error: ${errMessage(err)}` };
137
+ }
138
+ if (!res.ok) return { status: "unavailable", reason: `teams http ${res.status}` };
139
+ try {
140
+ const body = await res.json();
141
+ return {
142
+ status: "ok",
143
+ teams: (body.teams ?? []).map((t) => ({
144
+ id: t.id,
145
+ slug: t.slug,
146
+ name: t.name,
147
+ members: (t.members ?? []).map((m) => ({
148
+ userId: m.user_id,
149
+ orgMemberId: m.org_member_id,
150
+ teamRole: m.team_role,
151
+ email: m.email ?? null,
152
+ name: m.name ?? null
153
+ }))
154
+ }))
155
+ };
156
+ } catch (err) {
157
+ return { status: "unavailable", reason: `teams unparseable body: ${errMessage(err)}` };
158
+ }
97
159
  }
98
160
  };
99
161
  }