@oxygen-agent/cli 1.1010.954 → 1.1011.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. package/README.md +1 -1
  2. package/dist/command-manifest.js +2 -0
  3. package/dist/index.js +315 -40
  4. package/node_modules/@oxygen/cli-ugc/dist/commands.js +5 -5
  5. package/node_modules/@oxygen/cli-ugc/dist/field-parser.js +16 -1
  6. package/node_modules/@oxygen/shared/dist/capability-discovery.js +35 -5
  7. package/node_modules/@oxygen/shared/dist/company-reply-pause.d.ts +55 -0
  8. package/node_modules/@oxygen/shared/dist/company-reply-pause.js +146 -0
  9. package/node_modules/@oxygen/shared/dist/freemail-domains.d.ts +3 -3
  10. package/node_modules/@oxygen/shared/dist/freemail-domains.js +3 -3
  11. package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.d.ts +2 -0
  12. package/node_modules/@oxygen/shared/dist/linkedin-quota-denial.js +5 -0
  13. package/node_modules/@oxygen/shared/dist/plan-band.d.ts +7 -0
  14. package/node_modules/@oxygen/shared/dist/plan-band.js +24 -0
  15. package/node_modules/@oxygen/shared/dist/sequences.d.ts +54 -8
  16. package/node_modules/@oxygen/shared/dist/sequences.js +101 -9
  17. package/node_modules/@oxygen/shared/dist/signup-lead-payload.d.ts +1 -0
  18. package/node_modules/@oxygen/shared/dist/signup-lead-payload.js +3 -0
  19. package/node_modules/@oxygen/shared/dist/table-capacity.d.ts +2 -3
  20. package/node_modules/@oxygen/shared/dist/table-capacity.js +10 -16
  21. package/node_modules/@oxygen/shared/dist/version.generated.d.ts +1 -1
  22. package/node_modules/@oxygen/shared/dist/version.generated.js +1 -1
  23. package/node_modules/@oxygen/shared/dist/workspace-file-storage.d.ts +6 -0
  24. package/node_modules/@oxygen/shared/dist/workspace-file-storage.js +9 -1
  25. package/node_modules/@oxygen/shared/package.json +5 -0
  26. package/node_modules/@oxygen/workflows/dist/graph/lint.js +1 -1
  27. package/package.json +1 -1
@@ -1032,6 +1032,84 @@ export function sequenceChannels(definition) {
1032
1032
  * stored under `draft`. Empty means nothing to send, so starting would
1033
1033
  * either dispatch a blank message or fail per lead at send time.
1034
1034
  */
1035
+ /**
1036
+ * Threaded email steps that would continue another inbox's thread. A provider
1037
+ * thread belongs to the mailbox that opened it, so a step that changes From —
1038
+ * an exact-inbox step, or the return to the lead's assigned inbox after one —
1039
+ * must open a new thread under its own subject. Checked on save and again at
1040
+ * launch, because a draft may be saved half-written.
1041
+ */
1042
+ export function sequenceSenderThreadConflicts(steps) {
1043
+ const conflicts = [];
1044
+ let precedingSender;
1045
+ let seenEmail = false;
1046
+ steps.forEach((step, index) => {
1047
+ if (step.kind !== "email_send" && step.kind !== "email_reply")
1048
+ return;
1049
+ const sender = step.kind === "email_send" ? step.sender_mailbox_id : undefined;
1050
+ if (seenEmail && sequenceEmailStepThreads(step) && sender !== precedingSender) {
1051
+ conflicts.push({
1052
+ index,
1053
+ stepId: step.id,
1054
+ message: `step '${step.id}' changes the sending inbox. Give it a new subject to start a new thread.`,
1055
+ });
1056
+ }
1057
+ seenEmail = true;
1058
+ precedingSender = sender;
1059
+ });
1060
+ return conflicts;
1061
+ }
1062
+ /** LinkedIn kinds that open or continue a conversation, so bind the lead to one account. */
1063
+ export const LINKEDIN_RELATIONSHIP_STEP_KINDS = new Set(["invite", "message", "inmail", "withdraw_invite"]);
1064
+ /** LinkedIn light touches: they bind nothing, so each may come from any selected person. */
1065
+ export const LINKEDIN_LIGHT_TOUCH_STEP_KINDS = new Set(["visit_profile", "follow", "like_post", "comment_post"]);
1066
+ const LINKEDIN_SENDER_STEP_KINDS = new Set([...LINKEDIN_RELATIONSHIP_STEP_KINDS, ...LINKEDIN_LIGHT_TOUCH_STEP_KINDS]);
1067
+ function normalizeLinkedInStepSender(value, path, issues) {
1068
+ if (value === undefined || value === null || value === "")
1069
+ return {};
1070
+ if (typeof value !== "string" || !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(value.trim())) {
1071
+ issues.push({ path, message: "must be a sender profile id." });
1072
+ return {};
1073
+ }
1074
+ return { sender_profile_id: value.trim() };
1075
+ }
1076
+ function stepSenderProfileId(step) {
1077
+ return "sender_profile_id" in step && typeof step.sender_profile_id === "string" ? step.sender_profile_id : undefined;
1078
+ }
1079
+ /**
1080
+ * The person who owns every lead's LinkedIn conversation in this journey, when
1081
+ * the relationship steps name one (null = each lead's assigned person). Only
1082
+ * meaningful once sequenceLinkedInOwnerConflicts is empty.
1083
+ */
1084
+ export function sequenceLinkedInOwnerProfileId(steps) {
1085
+ for (const step of steps) {
1086
+ if (!LINKEDIN_RELATIONSHIP_STEP_KINDS.has(step.kind))
1087
+ continue;
1088
+ const owner = stepSenderProfileId(step);
1089
+ if (owner)
1090
+ return owner;
1091
+ }
1092
+ return null;
1093
+ }
1094
+ /**
1095
+ * Relationship steps that disagree about who sends them. LinkedIn lets one of
1096
+ * the workspace's accounts hold a conversation with a lead (and OXYGEN binds
1097
+ * the lead to the first account that invites or messages it), so a connection
1098
+ * request from one person followed by a message from another would be refused.
1099
+ */
1100
+ export function sequenceLinkedInOwnerConflicts(steps) {
1101
+ const relationship = steps
1102
+ .map((step, index) => ({ step, index }))
1103
+ .filter(({ step }) => LINKEDIN_RELATIONSHIP_STEP_KINDS.has(step.kind));
1104
+ const owners = new Set(relationship.map(({ step }) => stepSenderProfileId(step) ?? ""));
1105
+ if (owners.size <= 1)
1106
+ return [];
1107
+ return relationship.map(({ step, index }) => ({
1108
+ index,
1109
+ stepId: step.id,
1110
+ message: `step '${step.id}': every LinkedIn connection, message and InMail step must come from the same person (or all from the lead's own). LinkedIn lets one of your accounts hold a conversation with a lead.`,
1111
+ }));
1112
+ }
1035
1113
  export function sequenceStepsMissingCopy(steps) {
1036
1114
  const missing = [];
1037
1115
  const check = (step, field, value) => {
@@ -1174,6 +1252,12 @@ export function validateSequenceDefinition(input, options = {}) {
1174
1252
  message: `step '${step.id}' has no subject, which means "continue the previous email" — but no earlier step sends one. Give the first email of the sequence a subject.`,
1175
1253
  });
1176
1254
  });
1255
+ for (const conflict of sequenceSenderThreadConflicts(normalized)) {
1256
+ issues.push({ path: `steps[${conflict.index}].subject_template`, message: conflict.message });
1257
+ }
1258
+ for (const conflict of sequenceLinkedInOwnerConflicts(normalized)) {
1259
+ issues.push({ path: `steps[${conflict.index}].sender_profile_id`, message: conflict.message });
1260
+ }
1177
1261
  }
1178
1262
  if (issues.length > 0) {
1179
1263
  throw new OxygenError("invalid_sequence", `Sequence definition is invalid: ${issues.map((i) => `${i.path}: ${i.message}`).join("; ")}`, { details: { issues }, exitCode: 1 });
@@ -1298,14 +1382,17 @@ raw, index, options, issues) {
1298
1382
  if (channel && options.allowedChannels && !options.allowedChannels.includes(channel)) {
1299
1383
  issues.push({ path: `${path}.channel`, message: `channel '${channel}' is not enabled for this sequence.` });
1300
1384
  }
1385
+ const linkedInSender = LINKEDIN_SENDER_STEP_KINDS.has(stepKind)
1386
+ ? normalizeLinkedInStepSender(raw.sender_profile_id, `${path}.sender_profile_id`, issues)
1387
+ : {};
1301
1388
  switch (stepKind) {
1302
1389
  case "visit_profile":
1303
- return { id, channel: "linkedin", kind: "visit_profile" };
1390
+ return { id, channel: "linkedin", kind: "visit_profile", ...linkedInSender };
1304
1391
  case "invite": {
1305
1392
  const note = optionalTemplate(raw.note_template, `${path}.note_template`, MAX_NOTE_LENGTH, issues);
1306
1393
  return note !== undefined
1307
- ? { id, channel: "linkedin", kind: "invite", note_template: note }
1308
- : { id, channel: "linkedin", kind: "invite" };
1394
+ ? { id, channel: "linkedin", kind: "invite", note_template: note, ...linkedInSender }
1395
+ : { id, channel: "linkedin", kind: "invite", ...linkedInSender };
1309
1396
  }
1310
1397
  case "wait_for_connection": {
1311
1398
  const timeoutDays = raw.timeout_days === undefined || raw.timeout_days === null
@@ -1325,7 +1412,7 @@ raw, index, options, issues) {
1325
1412
  const autoOptimize = normalizeAutoOptimize(raw.auto_optimize, `${path}.auto_optimize`, variants, options, issues);
1326
1413
  const attachments = normalizeLinkedInAttachments(raw.attachments, `${path}.attachments`, issues);
1327
1414
  return {
1328
- id, channel: "linkedin", kind: "message", template: template ?? "",
1415
+ id, channel: "linkedin", kind: "message", template: template ?? "", ...linkedInSender,
1329
1416
  ...(variants ? { variants: variants } : {}),
1330
1417
  ...(autoOptimize ? { auto_optimize: autoOptimize } : {}),
1331
1418
  ...(attachments ? { attachments } : {}),
@@ -1337,7 +1424,7 @@ raw, index, options, issues) {
1337
1424
  const variants = normalizeVariants(raw.variants, `${path}.variants`, ["subject_template", "template"], issues, options.draft === true);
1338
1425
  const autoOptimize = normalizeAutoOptimize(raw.auto_optimize, `${path}.auto_optimize`, variants, options, issues);
1339
1426
  return {
1340
- id, channel: "linkedin", kind: "inmail", subject_template: subject ?? "", template: template ?? "",
1427
+ id, channel: "linkedin", kind: "inmail", subject_template: subject ?? "", template: template ?? "", ...linkedInSender,
1341
1428
  ...(variants ? { variants: variants } : {}),
1342
1429
  ...(autoOptimize ? { auto_optimize: autoOptimize } : {}),
1343
1430
  };
@@ -1372,8 +1459,13 @@ raw, index, options, issues) {
1372
1459
  const cc = optionalEmailList(raw.cc, `${path}.cc`, issues);
1373
1460
  const bcc = optionalEmailList(raw.bcc, `${path}.bcc`, issues);
1374
1461
  const sendWindow = normalizeSendWindow(raw.send_window, `${path}.send_window`, issues);
1462
+ const senderMailboxId = optionalString(raw.sender_mailbox_id, `${path}.sender_mailbox_id`, issues);
1463
+ if (senderMailboxId && !/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i.test(senderMailboxId)) {
1464
+ issues.push({ path: `${path}.sender_mailbox_id`, message: "must be an inbox id." });
1465
+ }
1375
1466
  return {
1376
1467
  id, channel: "email", kind: "email_send",
1468
+ ...(senderMailboxId ? { sender_mailbox_id: senderMailboxId } : {}),
1377
1469
  ...(threaded ? {} : { subject_template: subject }),
1378
1470
  body_template: body ?? "",
1379
1471
  ...(bodyHtml !== undefined ? { body_html_template: bodyHtml } : {}),
@@ -1557,7 +1649,7 @@ raw, index, options, issues) {
1557
1649
  };
1558
1650
  }
1559
1651
  case "follow":
1560
- return { id, channel: "linkedin", kind: "follow" };
1652
+ return { id, channel: "linkedin", kind: "follow", ...linkedInSender };
1561
1653
  case "like_post": {
1562
1654
  const reaction = typeof raw.reaction === "string" && LINKEDIN_POST_REACTION_SET.has(raw.reaction)
1563
1655
  ? raw.reaction
@@ -1567,7 +1659,7 @@ raw, index, options, issues) {
1567
1659
  }
1568
1660
  const recency = optionalPostRecencyDays(raw.post_recency_days, `${path}.post_recency_days`, issues);
1569
1661
  return {
1570
- id, channel: "linkedin", kind: "like_post",
1662
+ id, channel: "linkedin", kind: "like_post", ...linkedInSender,
1571
1663
  ...(reaction ? { reaction: reaction } : {}),
1572
1664
  ...(recency !== undefined ? { post_recency_days: recency } : {}),
1573
1665
  ...(raw.on_no_post === "stop" ? { on_no_post: "stop" } : {}),
@@ -1587,7 +1679,7 @@ raw, index, options, issues) {
1587
1679
  }
1588
1680
  const recency = optionalPostRecencyDays(raw.post_recency_days, `${path}.post_recency_days`, issues);
1589
1681
  return {
1590
- id, channel: "linkedin", kind: "comment_post",
1682
+ id, channel: "linkedin", kind: "comment_post", ...linkedInSender,
1591
1683
  ...(hasText ? { text_template: textTemplate } : {}),
1592
1684
  ...(hasPrompt ? { ai_prompt: aiPrompt } : {}),
1593
1685
  ...(recency !== undefined ? { post_recency_days: recency } : {}),
@@ -1595,7 +1687,7 @@ raw, index, options, issues) {
1595
1687
  };
1596
1688
  }
1597
1689
  case "withdraw_invite":
1598
- return { id, channel: "linkedin", kind: "withdraw_invite" };
1690
+ return { id, channel: "linkedin", kind: "withdraw_invite", ...linkedInSender };
1599
1691
  case "stop":
1600
1692
  return { id, kind: "stop" };
1601
1693
  default:
@@ -15,6 +15,7 @@ export type SignupLeadWebhookInput = {
15
15
  email?: string | null;
16
16
  firstName?: string | null;
17
17
  lastName?: string | null;
18
+ phone?: string | null;
18
19
  createdAt?: Date | string | number | null | undefined;
19
20
  /** A profile URL the provider already knows for this person; the consent-recorded creator URL still wins at enqueue. */
20
21
  linkedinUrl?: string | null;
@@ -33,6 +33,7 @@ export function buildSignupLeadWebhookPayload(input) {
33
33
  const email = normalizeString(input.user?.email)?.toLowerCase() ?? null;
34
34
  const firstName = normalizeString(input.user?.firstName);
35
35
  const lastName = normalizeString(input.user?.lastName);
36
+ const phone = normalizeString(input.user?.phone);
36
37
  const fullName = normalizeString([firstName, lastName].filter(Boolean).join(" "));
37
38
  const clerkOrgId = normalizeString(input.organization?.clerkOrgId);
38
39
  const companyName = normalizeString(input.organization?.name);
@@ -51,6 +52,7 @@ export function buildSignupLeadWebhookPayload(input) {
51
52
  email,
52
53
  first_name: firstName,
53
54
  last_name: lastName,
55
+ phone,
54
56
  full_name: fullName,
55
57
  created_at: normalizeDate(input.user?.createdAt),
56
58
  linkedin_url: normalizeString(input.user?.linkedinUrl) ?? undefined,
@@ -118,6 +120,7 @@ export function buildSignupLeadWebhookPayload(input) {
118
120
  email,
119
121
  first_name: firstName,
120
122
  last_name: lastName,
123
+ phone,
121
124
  full_name: fullName,
122
125
  company_name: companyName,
123
126
  company_domain: companyDomain,
@@ -37,9 +37,8 @@ export declare const PLAN_BANDS_AWAITING_TABLE_ROW_CAPACITY_TEST: readonly PlanB
37
37
  * - A limit that tightens (free, $49, $99) is a scheduled 2026-09 repricing value
38
38
  * (`storage.<band>.*` in `REPRICING_2026_09_SCHEDULE`): today's envelope until
39
39
  * the effective-date switch, the ratified ladder from it on.
40
- * - A band awaiting the rows-per-Table capacity test has its ladder workspace
41
- * totals and keeps today's rows per Table, except on the self-hosted dev
42
- * deployment, where the test runs.
40
+ * - A band awaiting a passing rows-per-Table capacity test has its ladder
41
+ * workspace totals and keeps today's rows per Table everywhere.
43
42
  * - Every other band is already at its ladder value.
44
43
  */
45
44
  export declare function resolveWorkspaceTableCapacity(band: PlanBand, options?: RepricingOptions): WorkspaceTableCapacityLimits;
@@ -50,18 +50,13 @@ export const PLAN_BAND_TABLE_CAPACITY_TARGETS = Object.freeze({
50
50
  */
51
51
  export const PLAN_BANDS_AWAITING_TABLE_ROW_CAPACITY_TEST = Object.freeze(["999", "1999"]);
52
52
  /**
53
- * The capacity test itself runs on the self-hosted dev deployment, which
54
- * therefore applies the awaiting bands' full ladder: a 10,000,000-row Table
55
- * cannot be measured behind a 3,000,000-row limit. Every other deployment,
56
- * production included, keeps the list above. The exact selector is compared
57
- * (not `resolveDeployEnv`, which throws on a typo) so a malformed value keeps
58
- * the production answer instead of failing a limits read.
53
+ * The dev capacity test has finished without meeting the latency and export
54
+ * requirements (decision F.9). Hold the same 3M limit in every environment;
55
+ * the earlier dev-only 5M/10M override made limits reads promise a capacity
56
+ * that the founder held after that test.
59
57
  */
60
- function awaitsTableRowCapacityTest(band, options) {
61
- if (!PLAN_BANDS_AWAITING_TABLE_ROW_CAPACITY_TEST.includes(band))
62
- return false;
63
- const env = options.env ?? process.env;
64
- return env.OXYGEN_DEPLOY_ENV?.trim().toLowerCase() !== "selfhost-dev";
58
+ function awaitsTableRowCapacityTest(band) {
59
+ return PLAN_BANDS_AWAITING_TABLE_ROW_CAPACITY_TEST.includes(band);
65
60
  }
66
61
  const STORAGE_FIELDS = [
67
62
  ["tableRowLimit", "table_row_limit"],
@@ -75,14 +70,13 @@ const STORAGE_FIELDS = [
75
70
  * - A limit that tightens (free, $49, $99) is a scheduled 2026-09 repricing value
76
71
  * (`storage.<band>.*` in `REPRICING_2026_09_SCHEDULE`): today's envelope until
77
72
  * the effective-date switch, the ratified ladder from it on.
78
- * - A band awaiting the rows-per-Table capacity test has its ladder workspace
79
- * totals and keeps today's rows per Table, except on the self-hosted dev
80
- * deployment, where the test runs.
73
+ * - A band awaiting a passing rows-per-Table capacity test has its ladder
74
+ * workspace totals and keeps today's rows per Table everywhere.
81
75
  * - Every other band is already at its ladder value.
82
76
  */
83
77
  export function resolveWorkspaceTableCapacity(band, options = {}) {
84
78
  const target = PLAN_BAND_TABLE_CAPACITY_TARGETS[band];
85
- if (awaitsTableRowCapacityTest(band, options)) {
79
+ if (awaitsTableRowCapacityTest(band)) {
86
80
  return Object.freeze({ ...target, tableRowLimit: WORKSPACE_TABLE_CAPACITY.tableRowLimit });
87
81
  }
88
82
  const resolved = { ...target };
@@ -95,7 +89,7 @@ export function resolveWorkspaceTableCapacity(band, options = {}) {
95
89
  return Object.freeze(resolved);
96
90
  }
97
91
  export function describeWorkspaceTableCapacityStatus(band, options = {}) {
98
- if (awaitsTableRowCapacityTest(band, options)) {
92
+ if (awaitsTableRowCapacityTest(band)) {
99
93
  return { status: "awaiting_capacity_test", effectiveAt: null, target: PLAN_BAND_TABLE_CAPACITY_TARGETS[band] };
100
94
  }
101
95
  const state = readRepricingSwitch(options);
@@ -1 +1 @@
1
- export declare const OXYGEN_BUILD_VERSION = "1.1010.954";
1
+ export declare const OXYGEN_BUILD_VERSION = "1.1011.95";
@@ -1,2 +1,2 @@
1
1
  // GENERATED by scripts/ci/version-stamp.mjs from VERSION + git history. Do not edit; do not commit.
2
- export const OXYGEN_BUILD_VERSION = "1.1010.954";
2
+ export const OXYGEN_BUILD_VERSION = "1.1011.95";
@@ -79,3 +79,9 @@ export declare function putWorkspaceFileObject(input: {
79
79
  sizeBytes: number;
80
80
  sha256: string;
81
81
  }>;
82
+ /** Delete only an object key already proven to belong to this workspace file. */
83
+ export declare function deleteWorkspaceFileObject(input: {
84
+ organizationId: string;
85
+ fileId: string;
86
+ storageKey: string;
87
+ }): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import { Buffer } from "node:buffer";
2
2
  import { createHash } from "node:crypto";
3
- import { GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
3
+ import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
4
4
  import { getSignedUrl } from "@aws-sdk/s3-request-presigner";
5
5
  import { OxygenError } from "./cli-result.js";
6
6
  import { resolveObjectStorageClient } from "./object-storage.js";
@@ -171,3 +171,11 @@ export async function putWorkspaceFileObject(input) {
171
171
  await readWorkspaceFileObject({ ...input, storageKey, sizeBytes: bytes.byteLength, sha256 });
172
172
  return { storageKey, sizeBytes: bytes.byteLength, sha256 };
173
173
  }
174
+ /** Delete only an object key already proven to belong to this workspace file. */
175
+ export async function deleteWorkspaceFileObject(input) {
176
+ assertWorkspaceFileObjectKey(input);
177
+ const { client, bucket } = resolveObjectStorageClient();
178
+ await client.send(new DeleteObjectCommand({ Bucket: bucket, Key: input.storageKey }), {
179
+ abortSignal: AbortSignal.timeout(STORAGE_TIMEOUT_MS),
180
+ });
181
+ }
@@ -121,6 +121,11 @@
121
121
  "import": "./dist/dnc-identities.js",
122
122
  "default": "./dist/dnc-identities.js"
123
123
  },
124
+ "./company-reply-pause": {
125
+ "types": "./dist/company-reply-pause.d.ts",
126
+ "import": "./dist/company-reply-pause.js",
127
+ "default": "./dist/company-reply-pause.js"
128
+ },
124
129
  "./provider-funding-errors": {
125
130
  "types": "./dist/provider-funding-errors.d.ts",
126
131
  "import": "./dist/provider-funding-errors.js",
@@ -812,7 +812,7 @@ function lintToolNode(node, path, add, scope, options) {
812
812
  add(`${path}.mode`, "invalid_step_mode", "Node mode is invalid.");
813
813
  }
814
814
  else if (node.mode !== undefined && (node.effect !== "external_write" || node.mode !== "dry_run")) {
815
- add(`${path}.mode`, "unsupported_step_mode", "A tool-node mode is only supported as 'dry_run' on an external write.");
815
+ add(`${path}.mode`, "unsupported_step_mode", "Omit mode to run this step live. The only mode a tool node may set is 'dry_run', on an external write, and it previews without writing.");
816
816
  }
817
817
  if (node.effect === "external_write" && node.retry !== undefined) {
818
818
  add(`${path}.retry`, "unsafe_external_write_retry", "External writes cannot have a node retry policy because an ambiguous failure may already have landed.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oxygen-agent/cli",
3
- "version": "1.1010.954",
3
+ "version": "1.1011.95",
4
4
  "private": false,
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",