@infinitetoken/lumber-kit 0.5.1 → 0.6.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/README.md CHANGED
@@ -50,7 +50,7 @@ import { LogLevel, LogRecord, LogEncrypted, LogPayload, LogsQuery } from '@infin
50
50
  | `PUSH_OFF` | constant | A push threshold one past Fatal (5): log pushes off |
51
51
  | `PushThreshold` | type | A `LogLevel` or `PUSH_OFF`, stored as a preference's `minimumLevel` |
52
52
  | `normalizePushThreshold` | function | A threshold from any stored or sent value: past Fatal is off, not a number is Warn |
53
- | `PushData` | type | The `data` each push type carries (`log`, `note`, `device_link`, `key_check`, `key_waiting`, `keys_shared`) |
53
+ | `PushData` | type | The `data` each push type carries (`log`, `note`, `device_link`, `device_link_answered`, `key_check`, `key_waiting`, `keys_shared`) |
54
54
  | `parsePushData` | function | A push's data as `PushData`, or `null` for data no Lumber push sends |
55
55
 
56
56
  ### Socket events
@@ -100,6 +100,21 @@ import { LogLevel, LogRecord, LogEncrypted, LogPayload, LogsQuery } from '@infin
100
100
  | `AppKeyUpsertBody` | interface | Body of PUT /apps/:id/keys/:userId |
101
101
  | `KeyPin` | interface | An account key a user accepted for someone, with its MAC |
102
102
  | `HolderKeyCheck`, `ClaimantKeyCheck`, `KeyCheckResult` | interfaces | A key check as the holder and the person being confirmed read it |
103
+ | `DeviceLinkCreateBody`, `DeviceLinkCreatedRecord` | interfaces | POST /device-links: a new device asking for the account key, and its answer |
104
+ | `DeviceLinkRecord`, `DeviceLinkApproveBody` | interfaces | A link request as the new device polls it (the exchange's first message once a code is shown, then the account key and the tag proving a device that showed the code sent it), and the approval that carries them |
105
+ | `PendingDeviceLink` | interface | A link request as a device holding the account key reads it, to show its code and finish it |
106
+ | `DeviceLinkStatus`, `DeviceLinkPlatform` | types | `pending`, `approved`, `denied` or `failed` (a code that didn't match); what the request comes from |
107
+
108
+ ### Colors
109
+
110
+ | Export | Kind | Purpose |
111
+ |--------|------|---------|
112
+ | `logLevelColors` | object | Each `LogLevel`'s color, as the apps and the CLI draw it |
113
+ | `DEFAULT_ACCENT` | constant | The accent until someone picks their own (their account's `color`): amber `#FBBF24` |
114
+ | `APP_COLORS` | array | The twelve colors `nameColor` picks from |
115
+ | `nameColor` | function | A stable color for a name, ignoring case and surrounding spaces, the same on every device |
116
+ | `resolveAppColor` | function | An app's own color, or its name's |
117
+ | `personColor` | function | Someone else's color, from their user id |
103
118
 
104
119
  ### API keys
105
120
 
package/dist/index.d.mts CHANGED
@@ -1,3 +1,13 @@
1
+ /** Each log level's color: its label in the apps' log list, a level's heat on the heatmap, the CLI's level column. */
2
+ declare const logLevelColors: Record<number, string>;
3
+ /** The accent until someone picks their own (their account's `color`): amber, the wordmark's and the icon's. */
4
+ declare const DEFAULT_ACCENT = "#FBBF24";
5
+ /**
6
+ * Colors for an app without one of its own, and for other people, picked by `nameColor`: Tailwind's 500s, roughly
7
+ * evenly spaced around the wheel, vivid and mid-light enough to read on dark and light surfaces.
8
+ */
9
+ declare const APP_COLORS: readonly ["#EF4444", "#F97316", "#F59E0B", "#84CC16", "#22C55E", "#14B8A6", "#06B6D4", "#3B82F6", "#6366F1", "#8B5CF6", "#D946EF", "#EC4899"];
10
+
1
11
  /**
2
12
  * Machine-readable reasons the API refuses a request, sent as `code` beside `message` where one status means more than
3
13
  * one thing. Messages are for people and may change; clients decide by the code.
@@ -212,7 +222,24 @@ interface AppsQuery {
212
222
  cursor?: string;
213
223
  }
214
224
 
215
- type DeviceLinkStatus = 'pending' | 'approved' | 'denied';
225
+ /**
226
+ * A device link request: a new device asking for the account key from a device that has it. `failed` is a code that
227
+ * didn't match, which ends the request.
228
+ */
229
+ type DeviceLinkStatus = 'pending' | 'approved' | 'denied' | 'failed';
230
+ /** What a request comes from, so the device answering it can say so and offer a QR code only to a phone's camera. */
231
+ type DeviceLinkPlatform = 'ios' | 'android' | 'web' | 'desktop' | 'cli';
232
+ /**
233
+ * Body of POST /device-links.
234
+ */
235
+ interface DeviceLinkCreateBody {
236
+ deviceName: string;
237
+ platform: DeviceLinkPlatform;
238
+ /** The public half of the new device's one-off X25519 keypair, which the account key is wrapped to. */
239
+ publicKey: string;
240
+ /** The new device's push device id, so the push asking the account's devices to link it skips it. */
241
+ deviceId?: string;
242
+ }
216
243
  /**
217
244
  * Response from POST /device-links.
218
245
  */
@@ -221,14 +248,48 @@ interface DeviceLinkCreatedRecord {
221
248
  expiresAt: string;
222
249
  }
223
250
  /**
224
- * Response from GET /device-links/:id.
225
- * encryptedKey is present only when status is 'approved'.
251
+ * Body of POST /device-links/:id/approve: the account key wrapped to the new device, and a tag over it made from the
252
+ * code's secret, which the new device checks before storing the key, so the server can't hand it a key of its own.
253
+ */
254
+ interface DeviceLinkApproveBody {
255
+ encryptedKey: string;
256
+ deliveryTag: string;
257
+ }
258
+ /**
259
+ * Response from GET /device-links/:id, as the new device reads it.
260
+ * encryptedKey and deliveryTag are present only when status is 'approved'. sid and holderMessage are set once a device
261
+ * holding the account key shows the request's code, and answeredAt once the new device has answered it.
226
262
  */
227
263
  interface DeviceLinkRecord {
228
264
  id: string;
229
265
  status: DeviceLinkStatus;
230
266
  encryptedKey?: string;
267
+ deliveryTag?: string;
268
+ expiresAt: string;
269
+ sid?: string | null;
270
+ holderMessage?: string | null;
271
+ answeredAt?: string | null;
272
+ }
273
+ /**
274
+ * A request waiting on the caller's account key (GET /device-links/pending), as a device holding it reads it: enough
275
+ * to show its code, and to finish it once answered. The messages are the same password-authenticated exchange as a key
276
+ * check's, so the server can't read or test codes against them. holderState is the code's side of the exchange, sealed
277
+ * under the account key, so any of the account's devices can show the same code or finish the request.
278
+ */
279
+ interface PendingDeviceLink {
280
+ id: string;
281
+ deviceName: string;
282
+ platform: DeviceLinkPlatform;
283
+ /** The new device's one-off public key, which an answer's tag has to cover before anything is wrapped to it. */
284
+ publicKey: string;
285
+ sid: string | null;
286
+ holderMessage: string | null;
287
+ holderState: string | null;
288
+ claimantMessage: string | null;
289
+ tag: string | null;
290
+ answeredAt: string | null;
231
291
  expiresAt: string;
292
+ createdAt: string;
232
293
  }
233
294
 
234
295
  /**
@@ -461,6 +522,12 @@ type PushData =
461
522
  deviceLinkId: string;
462
523
  deviceName: string;
463
524
  }
525
+ /** The new device entered its link code, which a device holding the account key now checks (Lumber-API). */
526
+ | {
527
+ type: 'device_link_answered';
528
+ deviceLinkId: string;
529
+ deviceName: string;
530
+ }
464
531
  /** The person the reader gave a code entered it (Lumber-API). */
465
532
  | {
466
533
  type: 'key_check';
@@ -610,6 +677,15 @@ interface UserRecord {
610
677
  declare const isValidAppearance: (value: unknown) => value is Appearance;
611
678
  /** Accepts 3- and 6-digit hex color strings (#rgb, #rrggbb). */
612
679
  declare const isValidColor: (value: string) => boolean;
680
+ /**
681
+ * One of APP_COLORS for a name, ignoring case and surrounding spaces. Nothing is stored: every device works out the
682
+ * same color.
683
+ */
684
+ declare const nameColor: (name: string) => string;
685
+ /** An app's color: the one its owner picked, or its name's. */
686
+ declare const resolveAppColor: (name: string, color?: string | null) => string;
687
+ /** Someone else's color, from their user id, since their own accent lives on their account. */
688
+ declare const personColor: (userId: string) => string;
613
689
  declare const isValidLogLevel: (value: unknown) => value is LogLevel;
614
690
  declare const isValidSubscriptionLevel: (value: unknown) => value is SubscriptionLevel;
615
691
  declare const isValidDeviceLinkStatus: (value: unknown) => value is DeviceLinkStatus;
@@ -638,4 +714,4 @@ declare const apiErrorCodeOf: (body: unknown) => ApiErrorCode | null;
638
714
  /** A push notification's data as PushData, or null for data no Lumber push sends. */
639
715
  declare const parsePushData: (data: unknown) => PushData | null;
640
716
 
641
- export { type ApiErrorBody, type ApiErrorCode, type ApiKeyRecord, type ApiResponse, type AppCreatedRecord, type AppEncryptedKey, type AppKeyUpsertBody, type AppPermission, type AppPersonRecord, type AppRecord, type AppTeamAccess, type Appearance, type AppsQuery, type ClaimantKeyCheck, type ClientToServerEvents, DEFAULT_PUSH_THRESHOLD, type DeviceLinkCreatedRecord, type DeviceLinkRecord, type DeviceLinkStatus, type FlagRecord, type FlagsQuery, type GuestRecord, type HolderKeyCheck, type InvitationRecord, type KeyCheckResult, type KeyHolder, type KeyPin, type LogContext, type LogEncrypted, LogLevel, type LogLevelName, type LogPayload, type LogRecord, type LogTimeseriesQuery, type LogsQuery, type NoteEncrypted, type NoteRecord, type NotesQuery, type NotificationPreference, type NotificationPreferences, PUSH_OFF, type PendingAppKey, type PushData, type PushThreshold, type PushToken, type PushType, type ResponseMetadata, type ServerToClientEvents, type SocketAppPayload, SubscriptionLevel, type SubscriptionLevelName, type SubscriptionLimits, type SubscriptionPlan, type SubscriptionPlanRecord, type TeamMemberRecord, type TeamRecord, type TeamsQuery, type TimeseriesBucket, type TimeseriesResponse, type UserRecord, apiErrorCodeOf, apiErrorCodes, isApiErrorCode, isValidAppearance, isValidColor, isValidDeviceLinkStatus, isValidImageUrl, isValidLogLevel, isValidSubscriptionLevel, isValidTeamMemberRole, joinApiKey, logLevelNameToLevel, logLevelToName, logLevels, normalizePushThreshold, parsePushData, splitApiKey, subscriptionLevelToName, subscriptionLevels, subscriptionLimits, subscriptionPlans };
717
+ export { APP_COLORS, type ApiErrorBody, type ApiErrorCode, type ApiKeyRecord, type ApiResponse, type AppCreatedRecord, type AppEncryptedKey, type AppKeyUpsertBody, type AppPermission, type AppPersonRecord, type AppRecord, type AppTeamAccess, type Appearance, type AppsQuery, type ClaimantKeyCheck, type ClientToServerEvents, DEFAULT_ACCENT, DEFAULT_PUSH_THRESHOLD, type DeviceLinkApproveBody, type DeviceLinkCreateBody, type DeviceLinkCreatedRecord, type DeviceLinkPlatform, type DeviceLinkRecord, type DeviceLinkStatus, type FlagRecord, type FlagsQuery, type GuestRecord, type HolderKeyCheck, type InvitationRecord, type KeyCheckResult, type KeyHolder, type KeyPin, type LogContext, type LogEncrypted, LogLevel, type LogLevelName, type LogPayload, type LogRecord, type LogTimeseriesQuery, type LogsQuery, type NoteEncrypted, type NoteRecord, type NotesQuery, type NotificationPreference, type NotificationPreferences, PUSH_OFF, type PendingAppKey, type PendingDeviceLink, type PushData, type PushThreshold, type PushToken, type PushType, type ResponseMetadata, type ServerToClientEvents, type SocketAppPayload, SubscriptionLevel, type SubscriptionLevelName, type SubscriptionLimits, type SubscriptionPlan, type SubscriptionPlanRecord, type TeamMemberRecord, type TeamRecord, type TeamsQuery, type TimeseriesBucket, type TimeseriesResponse, type UserRecord, apiErrorCodeOf, apiErrorCodes, isApiErrorCode, isValidAppearance, isValidColor, isValidDeviceLinkStatus, isValidImageUrl, isValidLogLevel, isValidSubscriptionLevel, isValidTeamMemberRole, joinApiKey, logLevelColors, logLevelNameToLevel, logLevelToName, logLevels, nameColor, normalizePushThreshold, parsePushData, personColor, resolveAppColor, splitApiKey, subscriptionLevelToName, subscriptionLevels, subscriptionLimits, subscriptionPlans };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,13 @@
1
+ /** Each log level's color: its label in the apps' log list, a level's heat on the heatmap, the CLI's level column. */
2
+ declare const logLevelColors: Record<number, string>;
3
+ /** The accent until someone picks their own (their account's `color`): amber, the wordmark's and the icon's. */
4
+ declare const DEFAULT_ACCENT = "#FBBF24";
5
+ /**
6
+ * Colors for an app without one of its own, and for other people, picked by `nameColor`: Tailwind's 500s, roughly
7
+ * evenly spaced around the wheel, vivid and mid-light enough to read on dark and light surfaces.
8
+ */
9
+ declare const APP_COLORS: readonly ["#EF4444", "#F97316", "#F59E0B", "#84CC16", "#22C55E", "#14B8A6", "#06B6D4", "#3B82F6", "#6366F1", "#8B5CF6", "#D946EF", "#EC4899"];
10
+
1
11
  /**
2
12
  * Machine-readable reasons the API refuses a request, sent as `code` beside `message` where one status means more than
3
13
  * one thing. Messages are for people and may change; clients decide by the code.
@@ -212,7 +222,24 @@ interface AppsQuery {
212
222
  cursor?: string;
213
223
  }
214
224
 
215
- type DeviceLinkStatus = 'pending' | 'approved' | 'denied';
225
+ /**
226
+ * A device link request: a new device asking for the account key from a device that has it. `failed` is a code that
227
+ * didn't match, which ends the request.
228
+ */
229
+ type DeviceLinkStatus = 'pending' | 'approved' | 'denied' | 'failed';
230
+ /** What a request comes from, so the device answering it can say so and offer a QR code only to a phone's camera. */
231
+ type DeviceLinkPlatform = 'ios' | 'android' | 'web' | 'desktop' | 'cli';
232
+ /**
233
+ * Body of POST /device-links.
234
+ */
235
+ interface DeviceLinkCreateBody {
236
+ deviceName: string;
237
+ platform: DeviceLinkPlatform;
238
+ /** The public half of the new device's one-off X25519 keypair, which the account key is wrapped to. */
239
+ publicKey: string;
240
+ /** The new device's push device id, so the push asking the account's devices to link it skips it. */
241
+ deviceId?: string;
242
+ }
216
243
  /**
217
244
  * Response from POST /device-links.
218
245
  */
@@ -221,14 +248,48 @@ interface DeviceLinkCreatedRecord {
221
248
  expiresAt: string;
222
249
  }
223
250
  /**
224
- * Response from GET /device-links/:id.
225
- * encryptedKey is present only when status is 'approved'.
251
+ * Body of POST /device-links/:id/approve: the account key wrapped to the new device, and a tag over it made from the
252
+ * code's secret, which the new device checks before storing the key, so the server can't hand it a key of its own.
253
+ */
254
+ interface DeviceLinkApproveBody {
255
+ encryptedKey: string;
256
+ deliveryTag: string;
257
+ }
258
+ /**
259
+ * Response from GET /device-links/:id, as the new device reads it.
260
+ * encryptedKey and deliveryTag are present only when status is 'approved'. sid and holderMessage are set once a device
261
+ * holding the account key shows the request's code, and answeredAt once the new device has answered it.
226
262
  */
227
263
  interface DeviceLinkRecord {
228
264
  id: string;
229
265
  status: DeviceLinkStatus;
230
266
  encryptedKey?: string;
267
+ deliveryTag?: string;
268
+ expiresAt: string;
269
+ sid?: string | null;
270
+ holderMessage?: string | null;
271
+ answeredAt?: string | null;
272
+ }
273
+ /**
274
+ * A request waiting on the caller's account key (GET /device-links/pending), as a device holding it reads it: enough
275
+ * to show its code, and to finish it once answered. The messages are the same password-authenticated exchange as a key
276
+ * check's, so the server can't read or test codes against them. holderState is the code's side of the exchange, sealed
277
+ * under the account key, so any of the account's devices can show the same code or finish the request.
278
+ */
279
+ interface PendingDeviceLink {
280
+ id: string;
281
+ deviceName: string;
282
+ platform: DeviceLinkPlatform;
283
+ /** The new device's one-off public key, which an answer's tag has to cover before anything is wrapped to it. */
284
+ publicKey: string;
285
+ sid: string | null;
286
+ holderMessage: string | null;
287
+ holderState: string | null;
288
+ claimantMessage: string | null;
289
+ tag: string | null;
290
+ answeredAt: string | null;
231
291
  expiresAt: string;
292
+ createdAt: string;
232
293
  }
233
294
 
234
295
  /**
@@ -461,6 +522,12 @@ type PushData =
461
522
  deviceLinkId: string;
462
523
  deviceName: string;
463
524
  }
525
+ /** The new device entered its link code, which a device holding the account key now checks (Lumber-API). */
526
+ | {
527
+ type: 'device_link_answered';
528
+ deviceLinkId: string;
529
+ deviceName: string;
530
+ }
464
531
  /** The person the reader gave a code entered it (Lumber-API). */
465
532
  | {
466
533
  type: 'key_check';
@@ -610,6 +677,15 @@ interface UserRecord {
610
677
  declare const isValidAppearance: (value: unknown) => value is Appearance;
611
678
  /** Accepts 3- and 6-digit hex color strings (#rgb, #rrggbb). */
612
679
  declare const isValidColor: (value: string) => boolean;
680
+ /**
681
+ * One of APP_COLORS for a name, ignoring case and surrounding spaces. Nothing is stored: every device works out the
682
+ * same color.
683
+ */
684
+ declare const nameColor: (name: string) => string;
685
+ /** An app's color: the one its owner picked, or its name's. */
686
+ declare const resolveAppColor: (name: string, color?: string | null) => string;
687
+ /** Someone else's color, from their user id, since their own accent lives on their account. */
688
+ declare const personColor: (userId: string) => string;
613
689
  declare const isValidLogLevel: (value: unknown) => value is LogLevel;
614
690
  declare const isValidSubscriptionLevel: (value: unknown) => value is SubscriptionLevel;
615
691
  declare const isValidDeviceLinkStatus: (value: unknown) => value is DeviceLinkStatus;
@@ -638,4 +714,4 @@ declare const apiErrorCodeOf: (body: unknown) => ApiErrorCode | null;
638
714
  /** A push notification's data as PushData, or null for data no Lumber push sends. */
639
715
  declare const parsePushData: (data: unknown) => PushData | null;
640
716
 
641
- export { type ApiErrorBody, type ApiErrorCode, type ApiKeyRecord, type ApiResponse, type AppCreatedRecord, type AppEncryptedKey, type AppKeyUpsertBody, type AppPermission, type AppPersonRecord, type AppRecord, type AppTeamAccess, type Appearance, type AppsQuery, type ClaimantKeyCheck, type ClientToServerEvents, DEFAULT_PUSH_THRESHOLD, type DeviceLinkCreatedRecord, type DeviceLinkRecord, type DeviceLinkStatus, type FlagRecord, type FlagsQuery, type GuestRecord, type HolderKeyCheck, type InvitationRecord, type KeyCheckResult, type KeyHolder, type KeyPin, type LogContext, type LogEncrypted, LogLevel, type LogLevelName, type LogPayload, type LogRecord, type LogTimeseriesQuery, type LogsQuery, type NoteEncrypted, type NoteRecord, type NotesQuery, type NotificationPreference, type NotificationPreferences, PUSH_OFF, type PendingAppKey, type PushData, type PushThreshold, type PushToken, type PushType, type ResponseMetadata, type ServerToClientEvents, type SocketAppPayload, SubscriptionLevel, type SubscriptionLevelName, type SubscriptionLimits, type SubscriptionPlan, type SubscriptionPlanRecord, type TeamMemberRecord, type TeamRecord, type TeamsQuery, type TimeseriesBucket, type TimeseriesResponse, type UserRecord, apiErrorCodeOf, apiErrorCodes, isApiErrorCode, isValidAppearance, isValidColor, isValidDeviceLinkStatus, isValidImageUrl, isValidLogLevel, isValidSubscriptionLevel, isValidTeamMemberRole, joinApiKey, logLevelNameToLevel, logLevelToName, logLevels, normalizePushThreshold, parsePushData, splitApiKey, subscriptionLevelToName, subscriptionLevels, subscriptionLimits, subscriptionPlans };
717
+ export { APP_COLORS, type ApiErrorBody, type ApiErrorCode, type ApiKeyRecord, type ApiResponse, type AppCreatedRecord, type AppEncryptedKey, type AppKeyUpsertBody, type AppPermission, type AppPersonRecord, type AppRecord, type AppTeamAccess, type Appearance, type AppsQuery, type ClaimantKeyCheck, type ClientToServerEvents, DEFAULT_ACCENT, DEFAULT_PUSH_THRESHOLD, type DeviceLinkApproveBody, type DeviceLinkCreateBody, type DeviceLinkCreatedRecord, type DeviceLinkPlatform, type DeviceLinkRecord, type DeviceLinkStatus, type FlagRecord, type FlagsQuery, type GuestRecord, type HolderKeyCheck, type InvitationRecord, type KeyCheckResult, type KeyHolder, type KeyPin, type LogContext, type LogEncrypted, LogLevel, type LogLevelName, type LogPayload, type LogRecord, type LogTimeseriesQuery, type LogsQuery, type NoteEncrypted, type NoteRecord, type NotesQuery, type NotificationPreference, type NotificationPreferences, PUSH_OFF, type PendingAppKey, type PendingDeviceLink, type PushData, type PushThreshold, type PushToken, type PushType, type ResponseMetadata, type ServerToClientEvents, type SocketAppPayload, SubscriptionLevel, type SubscriptionLevelName, type SubscriptionLimits, type SubscriptionPlan, type SubscriptionPlanRecord, type TeamMemberRecord, type TeamRecord, type TeamsQuery, type TimeseriesBucket, type TimeseriesResponse, type UserRecord, apiErrorCodeOf, apiErrorCodes, isApiErrorCode, isValidAppearance, isValidColor, isValidDeviceLinkStatus, isValidImageUrl, isValidLogLevel, isValidSubscriptionLevel, isValidTeamMemberRole, joinApiKey, logLevelColors, logLevelNameToLevel, logLevelToName, logLevels, nameColor, normalizePushThreshold, parsePushData, personColor, resolveAppColor, splitApiKey, subscriptionLevelToName, subscriptionLevels, subscriptionLimits, subscriptionPlans };
package/dist/index.js CHANGED
@@ -20,6 +20,8 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
20
20
  // src/index.ts
21
21
  var index_exports = {};
22
22
  __export(index_exports, {
23
+ APP_COLORS: () => APP_COLORS,
24
+ DEFAULT_ACCENT: () => DEFAULT_ACCENT,
23
25
  DEFAULT_PUSH_THRESHOLD: () => DEFAULT_PUSH_THRESHOLD,
24
26
  LogLevel: () => LogLevel,
25
27
  PUSH_OFF: () => PUSH_OFF,
@@ -35,11 +37,15 @@ __export(index_exports, {
35
37
  isValidSubscriptionLevel: () => isValidSubscriptionLevel,
36
38
  isValidTeamMemberRole: () => isValidTeamMemberRole,
37
39
  joinApiKey: () => joinApiKey,
40
+ logLevelColors: () => logLevelColors,
38
41
  logLevelNameToLevel: () => logLevelNameToLevel,
39
42
  logLevelToName: () => logLevelToName,
40
43
  logLevels: () => logLevels,
44
+ nameColor: () => nameColor,
41
45
  normalizePushThreshold: () => normalizePushThreshold,
42
46
  parsePushData: () => parsePushData,
47
+ personColor: () => personColor,
48
+ resolveAppColor: () => resolveAppColor,
43
49
  splitApiKey: () => splitApiKey,
44
50
  subscriptionLevelToName: () => subscriptionLevelToName,
45
51
  subscriptionLevels: () => subscriptionLevels,
@@ -48,24 +54,6 @@ __export(index_exports, {
48
54
  });
49
55
  module.exports = __toCommonJS(index_exports);
50
56
 
51
- // src/constants/errors.ts
52
- var apiErrorCodes = [
53
- /** 403: the team's plan has no room for another app. */
54
- "APP_LIMIT",
55
- /** 403: the team's plan has no room for another member (members and live invitations). */
56
- "SEAT_LIMIT",
57
- /** 403: the app's team's plan has no room for another guest on it (guests and live invitations). */
58
- "GUEST_LIMIT",
59
- /** 409: the person invited is already on the team. */
60
- "ALREADY_MEMBER",
61
- /** 409: the person invited can already read the app. */
62
- "ALREADY_READS",
63
- /** 409: the address invited already has an open invitation. */
64
- "ALREADY_INVITED",
65
- /** 409: the address invited already has an account, so invite the person instead. */
66
- "HAS_ACCOUNT"
67
- ];
68
-
69
57
  // src/constants/logLevel.ts
70
58
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
71
59
  LogLevel2[LogLevel2["Debug"] = 0] = "Debug";
@@ -91,6 +79,35 @@ var logLevelNameToLevel = {
91
79
  };
92
80
  var logLevels = [0 /* Debug */, 1 /* Info */, 2 /* Warn */, 3 /* Error */, 4 /* Fatal */];
93
81
 
82
+ // src/constants/colors.ts
83
+ var logLevelColors = {
84
+ [0 /* Debug */]: "#737373",
85
+ [1 /* Info */]: "#60A5FA",
86
+ [2 /* Warn */]: "#FBBF24",
87
+ [3 /* Error */]: "#F87171",
88
+ [4 /* Fatal */]: "#E879F9"
89
+ };
90
+ var DEFAULT_ACCENT = "#FBBF24";
91
+ var APP_COLORS = ["#EF4444", "#F97316", "#F59E0B", "#84CC16", "#22C55E", "#14B8A6", "#06B6D4", "#3B82F6", "#6366F1", "#8B5CF6", "#D946EF", "#EC4899"];
92
+
93
+ // src/constants/errors.ts
94
+ var apiErrorCodes = [
95
+ /** 403: the team's plan has no room for another app. */
96
+ "APP_LIMIT",
97
+ /** 403: the team's plan has no room for another member (members and live invitations). */
98
+ "SEAT_LIMIT",
99
+ /** 403: the app's team's plan has no room for another guest on it (guests and live invitations). */
100
+ "GUEST_LIMIT",
101
+ /** 409: the person invited is already on the team. */
102
+ "ALREADY_MEMBER",
103
+ /** 409: the person invited can already read the app. */
104
+ "ALREADY_READS",
105
+ /** 409: the address invited already has an open invitation. */
106
+ "ALREADY_INVITED",
107
+ /** 409: the address invited already has an account, so invite the person instead. */
108
+ "HAS_ACCOUNT"
109
+ ];
110
+
94
111
  // src/constants/notifications.ts
95
112
  var PUSH_OFF = 5;
96
113
  var DEFAULT_PUSH_THRESHOLD = 2 /* Warn */;
@@ -127,9 +144,20 @@ var subscriptionPlans = {
127
144
  var appearanceValues = ["system", "dark", "light"];
128
145
  var isValidAppearance = (value) => appearanceValues.includes(value);
129
146
  var isValidColor = (value) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value);
147
+ var hashName = (name) => {
148
+ let hash = 2166136261;
149
+ for (const char of name.trim().toLowerCase()) {
150
+ hash ^= char.codePointAt(0) ?? 0;
151
+ hash = Math.imul(hash, 16777619);
152
+ }
153
+ return hash >>> 0;
154
+ };
155
+ var nameColor = (name) => APP_COLORS[hashName(name) % APP_COLORS.length];
156
+ var resolveAppColor = (name, color) => color && isValidColor(color) ? color : nameColor(name);
157
+ var personColor = (userId) => nameColor(userId);
130
158
  var isValidLogLevel = (value) => logLevels.includes(value);
131
159
  var isValidSubscriptionLevel = (value) => subscriptionLevels.includes(value);
132
- var deviceLinkStatuses = ["pending", "approved", "denied"];
160
+ var deviceLinkStatuses = ["pending", "approved", "denied", "failed"];
133
161
  var isValidDeviceLinkStatus = (value) => deviceLinkStatuses.includes(value);
134
162
  var teamMemberRoles = ["owner", "admin", "member"];
135
163
  var isValidTeamMemberRole = (value) => teamMemberRoles.includes(value);
@@ -171,7 +199,8 @@ var parsePushData = (data) => {
171
199
  case "note":
172
200
  return isId(d.appId) && isId(d.noteId) ? { type: "note", appId: d.appId, noteId: d.noteId } : null;
173
201
  case "device_link":
174
- return isId(d.deviceLinkId) ? { type: "device_link", deviceLinkId: d.deviceLinkId, deviceName: typeof d.deviceName === "string" ? d.deviceName : "" } : null;
202
+ case "device_link_answered":
203
+ return isId(d.deviceLinkId) ? { type: d.type, deviceLinkId: d.deviceLinkId, deviceName: typeof d.deviceName === "string" ? d.deviceName : "" } : null;
175
204
  case "key_check":
176
205
  return isId(d.checkId) ? { type: "key_check", checkId: d.checkId } : null;
177
206
  case "key_waiting":
@@ -184,6 +213,8 @@ var parsePushData = (data) => {
184
213
  };
185
214
  // Annotate the CommonJS export names for ESM import in node:
186
215
  0 && (module.exports = {
216
+ APP_COLORS,
217
+ DEFAULT_ACCENT,
187
218
  DEFAULT_PUSH_THRESHOLD,
188
219
  LogLevel,
189
220
  PUSH_OFF,
@@ -199,11 +230,15 @@ var parsePushData = (data) => {
199
230
  isValidSubscriptionLevel,
200
231
  isValidTeamMemberRole,
201
232
  joinApiKey,
233
+ logLevelColors,
202
234
  logLevelNameToLevel,
203
235
  logLevelToName,
204
236
  logLevels,
237
+ nameColor,
205
238
  normalizePushThreshold,
206
239
  parsePushData,
240
+ personColor,
241
+ resolveAppColor,
207
242
  splitApiKey,
208
243
  subscriptionLevelToName,
209
244
  subscriptionLevels,
package/dist/index.mjs CHANGED
@@ -1,21 +1,3 @@
1
- // src/constants/errors.ts
2
- var apiErrorCodes = [
3
- /** 403: the team's plan has no room for another app. */
4
- "APP_LIMIT",
5
- /** 403: the team's plan has no room for another member (members and live invitations). */
6
- "SEAT_LIMIT",
7
- /** 403: the app's team's plan has no room for another guest on it (guests and live invitations). */
8
- "GUEST_LIMIT",
9
- /** 409: the person invited is already on the team. */
10
- "ALREADY_MEMBER",
11
- /** 409: the person invited can already read the app. */
12
- "ALREADY_READS",
13
- /** 409: the address invited already has an open invitation. */
14
- "ALREADY_INVITED",
15
- /** 409: the address invited already has an account, so invite the person instead. */
16
- "HAS_ACCOUNT"
17
- ];
18
-
19
1
  // src/constants/logLevel.ts
20
2
  var LogLevel = /* @__PURE__ */ ((LogLevel2) => {
21
3
  LogLevel2[LogLevel2["Debug"] = 0] = "Debug";
@@ -41,6 +23,35 @@ var logLevelNameToLevel = {
41
23
  };
42
24
  var logLevels = [0 /* Debug */, 1 /* Info */, 2 /* Warn */, 3 /* Error */, 4 /* Fatal */];
43
25
 
26
+ // src/constants/colors.ts
27
+ var logLevelColors = {
28
+ [0 /* Debug */]: "#737373",
29
+ [1 /* Info */]: "#60A5FA",
30
+ [2 /* Warn */]: "#FBBF24",
31
+ [3 /* Error */]: "#F87171",
32
+ [4 /* Fatal */]: "#E879F9"
33
+ };
34
+ var DEFAULT_ACCENT = "#FBBF24";
35
+ var APP_COLORS = ["#EF4444", "#F97316", "#F59E0B", "#84CC16", "#22C55E", "#14B8A6", "#06B6D4", "#3B82F6", "#6366F1", "#8B5CF6", "#D946EF", "#EC4899"];
36
+
37
+ // src/constants/errors.ts
38
+ var apiErrorCodes = [
39
+ /** 403: the team's plan has no room for another app. */
40
+ "APP_LIMIT",
41
+ /** 403: the team's plan has no room for another member (members and live invitations). */
42
+ "SEAT_LIMIT",
43
+ /** 403: the app's team's plan has no room for another guest on it (guests and live invitations). */
44
+ "GUEST_LIMIT",
45
+ /** 409: the person invited is already on the team. */
46
+ "ALREADY_MEMBER",
47
+ /** 409: the person invited can already read the app. */
48
+ "ALREADY_READS",
49
+ /** 409: the address invited already has an open invitation. */
50
+ "ALREADY_INVITED",
51
+ /** 409: the address invited already has an account, so invite the person instead. */
52
+ "HAS_ACCOUNT"
53
+ ];
54
+
44
55
  // src/constants/notifications.ts
45
56
  var PUSH_OFF = 5;
46
57
  var DEFAULT_PUSH_THRESHOLD = 2 /* Warn */;
@@ -77,9 +88,20 @@ var subscriptionPlans = {
77
88
  var appearanceValues = ["system", "dark", "light"];
78
89
  var isValidAppearance = (value) => appearanceValues.includes(value);
79
90
  var isValidColor = (value) => /^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$/.test(value);
91
+ var hashName = (name) => {
92
+ let hash = 2166136261;
93
+ for (const char of name.trim().toLowerCase()) {
94
+ hash ^= char.codePointAt(0) ?? 0;
95
+ hash = Math.imul(hash, 16777619);
96
+ }
97
+ return hash >>> 0;
98
+ };
99
+ var nameColor = (name) => APP_COLORS[hashName(name) % APP_COLORS.length];
100
+ var resolveAppColor = (name, color) => color && isValidColor(color) ? color : nameColor(name);
101
+ var personColor = (userId) => nameColor(userId);
80
102
  var isValidLogLevel = (value) => logLevels.includes(value);
81
103
  var isValidSubscriptionLevel = (value) => subscriptionLevels.includes(value);
82
- var deviceLinkStatuses = ["pending", "approved", "denied"];
104
+ var deviceLinkStatuses = ["pending", "approved", "denied", "failed"];
83
105
  var isValidDeviceLinkStatus = (value) => deviceLinkStatuses.includes(value);
84
106
  var teamMemberRoles = ["owner", "admin", "member"];
85
107
  var isValidTeamMemberRole = (value) => teamMemberRoles.includes(value);
@@ -121,7 +143,8 @@ var parsePushData = (data) => {
121
143
  case "note":
122
144
  return isId(d.appId) && isId(d.noteId) ? { type: "note", appId: d.appId, noteId: d.noteId } : null;
123
145
  case "device_link":
124
- return isId(d.deviceLinkId) ? { type: "device_link", deviceLinkId: d.deviceLinkId, deviceName: typeof d.deviceName === "string" ? d.deviceName : "" } : null;
146
+ case "device_link_answered":
147
+ return isId(d.deviceLinkId) ? { type: d.type, deviceLinkId: d.deviceLinkId, deviceName: typeof d.deviceName === "string" ? d.deviceName : "" } : null;
125
148
  case "key_check":
126
149
  return isId(d.checkId) ? { type: "key_check", checkId: d.checkId } : null;
127
150
  case "key_waiting":
@@ -133,6 +156,8 @@ var parsePushData = (data) => {
133
156
  }
134
157
  };
135
158
  export {
159
+ APP_COLORS,
160
+ DEFAULT_ACCENT,
136
161
  DEFAULT_PUSH_THRESHOLD,
137
162
  LogLevel,
138
163
  PUSH_OFF,
@@ -148,11 +173,15 @@ export {
148
173
  isValidSubscriptionLevel,
149
174
  isValidTeamMemberRole,
150
175
  joinApiKey,
176
+ logLevelColors,
151
177
  logLevelNameToLevel,
152
178
  logLevelToName,
153
179
  logLevels,
180
+ nameColor,
154
181
  normalizePushThreshold,
155
182
  parsePushData,
183
+ personColor,
184
+ resolveAppColor,
156
185
  splitApiKey,
157
186
  subscriptionLevelToName,
158
187
  subscriptionLevels,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@infinitetoken/lumber-kit",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
4
4
  "description": "Shared TypeScript types and wire format contracts for the Lumber cloud logging suite",
5
5
  "keywords": [
6
6
  "lumber",