@north-light/sdk 0.3.611 → 0.3.613

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.
@@ -263,6 +263,19 @@ export declare class OAuth2Client {
263
263
  * token request's: `invalid_client` is a 401 `unauthorized` naming the key, a directory 5xx or a 429 has `retryable` true.
264
264
  */
265
265
  directoryToken(rejected?: string): Promise<string>;
266
+ /**
267
+ * Whether `userId` (`user:<id>`) is on the team that holds this app, and their role on it (`GET /v1/apps/self/members/:user_id`
268
+ * as the app itself, bearer `directoryToken()`). For an app's own staff pages: the directory answers from its current
269
+ * relations and the SDK caches nothing, so someone removed from the team is refused at their next call. An unknown
270
+ * user is `{member: false}`. A 401 re-mints the directory token once. A directory 5xx or 429 throws with `retryable` true;
271
+ * the deadline is `timeoutMs` (`DeadlineExceededError`).
272
+ */
273
+ appMember(userId: string): Promise<{
274
+ member: true;
275
+ role: 'admin' | 'member';
276
+ } | {
277
+ member: false;
278
+ }>;
266
279
  /**
267
280
  * Follow the app's background runs until each comes to rest, and hand each ending to `onEnded` (see
268
281
  * `RunWatchOptions`). Runs are opened with `client(userId)`, so refusals reach `onRefusal`. Call `start()`.
@@ -480,6 +480,42 @@ export class OAuth2Client {
480
480
  directoryToken(rejected) {
481
481
  return this.directoryApiTokens.get('directory', rejected).then((token) => token.accessToken);
482
482
  }
483
+ /**
484
+ * Whether `userId` (`user:<id>`) is on the team that holds this app, and their role on it (`GET /v1/apps/self/members/:user_id`
485
+ * as the app itself, bearer `directoryToken()`). For an app's own staff pages: the directory answers from its current
486
+ * relations and the SDK caches nothing, so someone removed from the team is refused at their next call. An unknown
487
+ * user is `{member: false}`. A 401 re-mints the directory token once. A directory 5xx or 429 throws with `retryable` true;
488
+ * the deadline is `timeoutMs` (`DeadlineExceededError`).
489
+ */
490
+ async appMember(userId) {
491
+ if (!/^user:[^:]+$/.test(userId))
492
+ throw new CrouterError(`appMember needs a Northlight user id (user:<id>), got ${JSON.stringify(userId)}`);
493
+ const url = `${this.issuer.replace(/\/$/, '')}/v1/apps/self/members/${encodeURIComponent(userId)}`;
494
+ const deadlineAt = Date.now() + this.timeoutMs;
495
+ const ask = async (token) => this.bounded(url, { headers: { authorization: `Bearer ${token}` } }, deadlineAt, async (response) => ({ response, body: await response.json().catch(() => undefined) }));
496
+ const mint = async (rejected) => {
497
+ try {
498
+ return await withinDeadline(this.directoryToken(rejected), deadlineAt);
499
+ }
500
+ catch (error) {
501
+ throw error instanceof APIError ? mapError(error) : error;
502
+ }
503
+ };
504
+ const token = await mint();
505
+ let { response, body } = await ask(token);
506
+ if (response.status === 401)
507
+ ({ response, body } = await ask(await mint(token)));
508
+ const answer = body;
509
+ if (response.ok) {
510
+ if (answer?.member === false)
511
+ return { member: false };
512
+ if (answer?.member === true && (answer.role === 'admin' || answer.role === 'member'))
513
+ return { member: true, role: answer.role };
514
+ throw new CrouterError('Directory returned an invalid membership answer');
515
+ }
516
+ const error = answer?.error;
517
+ throw mapError(new APIError(response.status, typeof error?.code === 'string' ? error.code : 'directory_error', typeof error?.message === 'string' ? error.message : `Directory membership read returned ${response.status}`, undefined, response.headers, { origin: 'directory', ...(typeof error?.type === 'string' ? { type: error.type } : {}), retryable: response.status >= 500 || response.status === 429 }));
518
+ }
483
519
  /**
484
520
  * Follow the app's background runs until each comes to rest, and hand each ending to `onEnded` (see
485
521
  * `RunWatchOptions`). Runs are opened with `client(userId)`, so refusals reach `onRefusal`. Call `start()`.
@@ -2,7 +2,7 @@
2
2
  name: northlight
3
3
  description: "Use when building, debugging or shipping an app that signs people in with Northlight, starts runs on their Northlight runtime, or calls Northlight providers (sms, search, integrations, context, device) with @north-light/sdk."
4
4
  metadata:
5
- sdk-version: "0.3.611"
5
+ sdk-version: "0.3.613"
6
6
  ---
7
7
 
8
8
  # Building a Northlight app
@@ -27,7 +27,7 @@ Never text a person first: Comms refuses a send to anyone who has not texted you
27
27
 
28
28
  ## First: check this skill matches the SDK
29
29
 
30
- This skill is for SDK version `0.3.611`. If the project has `node_modules/@north-light/sdk/package.json`, compare its `version` with that number. If they differ, run `npx @north-light/sdk skill` in the project, then read the rewritten `.agents/skills/northlight/SKILL.md` (or `.claude/skills/northlight/SKILL.md`) instead of this copy. It needs no account. A project that has no SDK yet starts with `npx @north-light/sdk@latest init <dir>`.
30
+ This skill is for SDK version `0.3.613`. If the project has `node_modules/@north-light/sdk/package.json`, compare its `version` with that number. If they differ, run `npx @north-light/sdk skill` in the project, then read the rewritten `.agents/skills/northlight/SKILL.md` (or `.claude/skills/northlight/SKILL.md`) instead of this copy. It needs no account. A project that has no SDK yet starts with `npx @north-light/sdk@latest init <dir>`.
31
31
 
32
32
  ## Where things run
33
33
 
package/dist/version.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /** This SDK's release version. Informational only: compatibility is the contract level, not this number. */
2
- export declare const SDK_VERSION = "0.3.611";
2
+ export declare const SDK_VERSION = "0.3.613";
3
3
  /** The release sent in `Crouter-Minimum-Runtime-Version` on every request. It is frozen, so a runtime from before the
4
4
  * contract level existed (which only knows this header) refuses this SDK, while every later runtime accepts it. */
5
5
  export declare const LEGACY_FLOOR = "0.3.423";
package/dist/version.js CHANGED
@@ -1,6 +1,6 @@
1
1
  // SDK_VERSION is written by scripts/release-version.mjs on every release; do not edit by hand.
2
2
  /** This SDK's release version. Informational only: compatibility is the contract level, not this number. */
3
- export const SDK_VERSION = '0.3.611';
3
+ export const SDK_VERSION = '0.3.613';
4
4
  // LEGACY_FLOOR is written once by `scripts/contract.mjs release`, at the first release that carries the contract level
5
5
  // (the `0.0.0` placeholder is replaced there and never again); do not edit by hand.
6
6
  /** The release sent in `Crouter-Minimum-Runtime-Version` on every request. It is frozen, so a runtime from before the
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@north-light/sdk",
3
- "version": "0.3.611",
4
- "crouterContract": "1.46",
3
+ "version": "0.3.613",
4
+ "crouterContract": "1.47",
5
5
  "description": "Typed Node and browser client for running crouter agents through the crtrd /v1 API.",
6
6
  "type": "module",
7
7
  "main": "./dist/index.js",
@@ -44,9 +44,9 @@
44
44
  "prepublishOnly": "node -e \"if (process.env.npm_config_tag !== 'next') { console.error('@north-light/sdk publishes only with --tag next; scripts/promote-sdk.mjs is the only mover of latest.'); process.exit(1); }\""
45
45
  },
46
46
  "dependencies": {
47
- "@north-light/api": "0.3.611",
48
- "@north-light/identity": "0.3.611",
49
- "@north-light/plugin": "0.3.611",
47
+ "@north-light/api": "0.3.613",
48
+ "@north-light/identity": "0.3.613",
49
+ "@north-light/plugin": "0.3.613",
50
50
  "jose": "^6.2.1"
51
51
  },
52
52
  "peerDependencies": {