@modelnex/sdk 0.5.23 → 0.5.25

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
@@ -27,6 +27,7 @@ import { ModelNexProvider, ModelNexChatBubble } from '@modelnex/sdk';
27
27
  export default function AppShell() {
28
28
  return (
29
29
  <ModelNexProvider
30
+ serverUrl={import.meta.env.DEV ? 'http://localhost:3002' : 'https://api.modelnex.com'}
30
31
  websiteId="your-website-id"
31
32
  userProfile={{
32
33
  type: currentUser.role,
@@ -41,7 +42,7 @@ export default function AppShell() {
41
42
  }
42
43
  ```
43
44
 
44
- `websiteId` identifies the integration. `userProfile` is used for tour/workflow targeting and completion tracking.
45
+ `websiteId` identifies the integration. `userProfile` is used for tour/workflow targeting and completion tracking. For local development, pass `serverUrl` explicitly so the SDK does not fall back to the hosted API.
45
46
 
46
47
  ## Tour Start Model
47
48
 
@@ -82,6 +83,7 @@ Current behavior:
82
83
 
83
84
  | Prop | Purpose |
84
85
  |------|---------|
86
+ | `serverUrl` | Backend base URL for chat, tags, tours, voice, and recording APIs |
85
87
  | `websiteId` | Tenant/integration identifier |
86
88
  | `userProfile` | End-user targeting data for tours/workflows |
87
89
  | `devMode` | Enables recording/studio tooling for your internal users |
package/dist/index.js CHANGED
@@ -2461,6 +2461,7 @@ var import_react13 = require("react");
2461
2461
  // src/utils/draftPreview.ts
2462
2462
  var ACTIVE_DRAFT_PREVIEW_STORAGE_KEY = "modelnex:active-draft-preview";
2463
2463
  var PREVIEW_SESSION_SUPPRESSION_STORAGE_KEY = "modelnex:preview-session-suppressed";
2464
+ var SUPPRESSED_DRAFT_PREVIEW_STORAGE_KEY = "modelnex:suppressed-draft-preview";
2464
2465
  function getPreviewQueryParamName(experienceType) {
2465
2466
  return experienceType === "onboarding" ? "modelnex_test_workflow" : "modelnex_test_tour";
2466
2467
  }
@@ -2510,6 +2511,51 @@ function clearActiveDraftPreview(experienceType) {
2510
2511
  } catch {
2511
2512
  }
2512
2513
  }
2514
+ function readSavedDraftPreview(storageKey) {
2515
+ if (typeof window === "undefined") return null;
2516
+ try {
2517
+ const raw = window.sessionStorage.getItem(storageKey);
2518
+ if (!raw) return null;
2519
+ const parsed = JSON.parse(raw);
2520
+ if (!parsed || typeof parsed.id !== "string") return null;
2521
+ if (parsed.experienceType !== "tour" && parsed.experienceType !== "onboarding") return null;
2522
+ return {
2523
+ id: parsed.id,
2524
+ experienceType: parsed.experienceType
2525
+ };
2526
+ } catch {
2527
+ return null;
2528
+ }
2529
+ }
2530
+ function readSuppressedDraftPreview() {
2531
+ return readSavedDraftPreview(SUPPRESSED_DRAFT_PREVIEW_STORAGE_KEY);
2532
+ }
2533
+ function persistSuppressedDraftPreview(draft) {
2534
+ if (typeof window === "undefined") return;
2535
+ try {
2536
+ window.sessionStorage.setItem(SUPPRESSED_DRAFT_PREVIEW_STORAGE_KEY, JSON.stringify(draft));
2537
+ } catch {
2538
+ }
2539
+ }
2540
+ function clearSuppressedDraftPreview(draft) {
2541
+ if (typeof window === "undefined") return;
2542
+ try {
2543
+ const suppressedDraft = readSuppressedDraftPreview();
2544
+ if (draft && suppressedDraft) {
2545
+ if (suppressedDraft.id !== draft.id || suppressedDraft.experienceType !== draft.experienceType) {
2546
+ return;
2547
+ }
2548
+ }
2549
+ window.sessionStorage.removeItem(SUPPRESSED_DRAFT_PREVIEW_STORAGE_KEY);
2550
+ } catch {
2551
+ }
2552
+ }
2553
+ function isSuppressedDraftPreview(draft) {
2554
+ const suppressedDraft = readSuppressedDraftPreview();
2555
+ return Boolean(
2556
+ suppressedDraft && suppressedDraft.id === draft.id && suppressedDraft.experienceType === draft.experienceType
2557
+ );
2558
+ }
2513
2559
  function persistPreviewSessionSuppression() {
2514
2560
  if (typeof window === "undefined") return;
2515
2561
  try {
@@ -4090,7 +4136,13 @@ function useTourPlayback({
4090
4136
  });
4091
4137
  }
4092
4138
  if (reviewModeRef.current) {
4093
- clearActiveDraftPreview(experienceType);
4139
+ if (tourRef.current?.id) {
4140
+ persistSuppressedDraftPreview({
4141
+ id: tourRef.current.id,
4142
+ experienceType
4143
+ });
4144
+ }
4145
+ clearActiveDraftPreview();
4094
4146
  }
4095
4147
  setIsActive(false);
4096
4148
  setActiveTour(null);
@@ -4139,7 +4191,13 @@ function useTourPlayback({
4139
4191
  });
4140
4192
  }
4141
4193
  if (reviewModeRef.current) {
4142
- clearActiveDraftPreview(experienceType);
4194
+ if (endingTourId) {
4195
+ persistSuppressedDraftPreview({
4196
+ id: endingTourId,
4197
+ experienceType
4198
+ });
4199
+ }
4200
+ clearActiveDraftPreview();
4143
4201
  }
4144
4202
  onTourEnd?.();
4145
4203
  }, [experienceType, userProfile, serverUrl, voice, onTourEnd, websiteId, releasePlaybackOwnership]);
@@ -4175,6 +4233,10 @@ function useTourPlayback({
4175
4233
  setIsReviewMode(shouldReview);
4176
4234
  reviewModeRef.current = shouldReview;
4177
4235
  if (shouldReview) {
4236
+ clearSuppressedDraftPreview({
4237
+ id: tour.id,
4238
+ experienceType
4239
+ });
4178
4240
  try {
4179
4241
  const previewRun = await createPreviewRun(serverUrl, toursApiBaseRef.current, tour.id, {
4180
4242
  source: "sdk_test_preview",
@@ -4215,14 +4277,23 @@ function useTourPlayback({
4215
4277
  const persistedTourId = activeDraftPreview?.experienceType === experienceType ? activeDraftPreview.id : null;
4216
4278
  const tourId = queryTourId || persistedTourId;
4217
4279
  if (!tourId) return;
4280
+ const draftPreview = { id: tourId, experienceType };
4281
+ if (!queryTourId && isSuppressedDraftPreview(draftPreview)) {
4282
+ clearActiveDraftPreview(experienceType);
4283
+ return;
4284
+ }
4218
4285
  let cancelled = false;
4219
4286
  (async () => {
4220
4287
  try {
4221
- persistActiveDraftPreview({ id: tourId, experienceType });
4288
+ if (queryTourId) {
4289
+ clearSuppressedDraftPreview(draftPreview);
4290
+ }
4291
+ persistActiveDraftPreview(draftPreview);
4222
4292
  const tour = await fetchTourById(serverUrl, toursApiBase, tourId, experienceType, websiteId);
4223
4293
  if (cancelled) return;
4224
4294
  if (!tour) {
4225
4295
  clearActiveDraftPreview(experienceType);
4296
+ persistSuppressedDraftPreview(draftPreview);
4226
4297
  console.warn("[ModelNex] Tour fetch failed:", tourId, experienceType, "(Check ModelNex server is running and CORS allows this origin)");
4227
4298
  return;
4228
4299
  }
@@ -4464,6 +4535,9 @@ function useTourPlayback({
4464
4535
 
4465
4536
  // src/hooks/useExperiencePlaybackController.ts
4466
4537
  var EXPERIENCE_PRIORITY = ["onboarding", "tour"];
4538
+ function shouldClearDraftPreviewOnPromptDismiss(prompt) {
4539
+ return Boolean(prompt?.options?.reviewMode);
4540
+ }
4467
4541
  function shouldDiscoverDraftPreview({
4468
4542
  disabled,
4469
4543
  hasPendingPrompt,
@@ -4473,6 +4547,12 @@ function shouldDiscoverDraftPreview({
4473
4547
  }) {
4474
4548
  return !disabled && !hasPendingPrompt && !isPlaybackActive && !isPreviewDiscoveryInFlight && !isStartingExperience;
4475
4549
  }
4550
+ function shouldReplayDraftPreview({
4551
+ hasExplicitQueryParam,
4552
+ isSuppressedPreview
4553
+ }) {
4554
+ return hasExplicitQueryParam || !isSuppressedPreview;
4555
+ }
4476
4556
  function shouldDiscoverEligibleTours({
4477
4557
  disabled,
4478
4558
  hasPendingPrompt,
@@ -4558,6 +4638,13 @@ function useExperiencePlaybackController({
4558
4638
  if (experienceType && prompt.experienceType !== experienceType) return;
4559
4639
  setPendingPrompt(null);
4560
4640
  pendingPromptRef.current = null;
4641
+ if (shouldClearDraftPreviewOnPromptDismiss(prompt)) {
4642
+ clearActiveDraftPreview(prompt.experienceType);
4643
+ persistSuppressedDraftPreview({
4644
+ id: prompt.tour.id,
4645
+ experienceType: prompt.experienceType
4646
+ });
4647
+ }
4561
4648
  if (!userProfile?.userId) return;
4562
4649
  void markTourDismissed(
4563
4650
  serverUrl,
@@ -4637,17 +4724,32 @@ function useExperiencePlaybackController({
4637
4724
  }
4638
4725
  const request = previewRequests[0];
4639
4726
  if (!request) return;
4727
+ const draftPreview = {
4728
+ id: request.tourId,
4729
+ experienceType: request.experienceType
4730
+ };
4731
+ if (!shouldReplayDraftPreview({
4732
+ hasExplicitQueryParam: Boolean(request.queryParam),
4733
+ isSuppressedPreview: isSuppressedDraftPreview(draftPreview)
4734
+ })) {
4735
+ clearActiveDraftPreview(request.experienceType);
4736
+ return;
4737
+ }
4640
4738
  let cancelled = false;
4641
4739
  previewDiscoveryInFlightRef.current = true;
4642
4740
  previewSessionRef.current = true;
4643
4741
  persistPreviewSessionSuppression();
4644
4742
  (async () => {
4645
4743
  try {
4646
- persistActiveDraftPreview({ id: request.tourId, experienceType: request.experienceType });
4744
+ if (request.queryParam) {
4745
+ clearSuppressedDraftPreview(draftPreview);
4746
+ }
4747
+ persistActiveDraftPreview(draftPreview);
4647
4748
  const tour = await fetchTourById(serverUrl, toursApiBase, request.tourId, request.experienceType, websiteId);
4648
4749
  if (cancelled) return;
4649
4750
  if (!tour) {
4650
4751
  clearActiveDraftPreview(request.experienceType);
4752
+ persistSuppressedDraftPreview(draftPreview);
4651
4753
  return;
4652
4754
  }
4653
4755
  if (request.queryParam) {
@@ -11142,6 +11244,13 @@ var ModelNexProvider = ({
11142
11244
  const serverUrl = serverUrlProp ?? DEFAULT_MODELNEX_SERVER_URL;
11143
11245
  const commandUrl = void 0;
11144
11246
  const disableSocket = false;
11247
+ (0, import_react21.useEffect)(() => {
11248
+ if (process.env.NODE_ENV !== "production" && !serverUrlProp) {
11249
+ console.warn(
11250
+ `[ModelNex SDK] ModelNexProvider is using the default server URL (${DEFAULT_MODELNEX_SERVER_URL}). Pass \`serverUrl\` explicitly in local development to avoid accidentally targeting the hosted backend.`
11251
+ );
11252
+ }
11253
+ }, [serverUrlProp]);
11145
11254
  const renderedChildren = children;
11146
11255
  const [activeAgentActions, setActiveAgentActions] = (0, import_react21.useState)(/* @__PURE__ */ new Set());
11147
11256
  const [stagingFields, setStagingFields] = (0, import_react21.useState)(/* @__PURE__ */ new Set());
package/dist/index.mjs CHANGED
@@ -2251,6 +2251,7 @@ import { useCallback as useCallback8, useEffect as useEffect12, useRef as useRef
2251
2251
  // src/utils/draftPreview.ts
2252
2252
  var ACTIVE_DRAFT_PREVIEW_STORAGE_KEY = "modelnex:active-draft-preview";
2253
2253
  var PREVIEW_SESSION_SUPPRESSION_STORAGE_KEY = "modelnex:preview-session-suppressed";
2254
+ var SUPPRESSED_DRAFT_PREVIEW_STORAGE_KEY = "modelnex:suppressed-draft-preview";
2254
2255
  function getPreviewQueryParamName(experienceType) {
2255
2256
  return experienceType === "onboarding" ? "modelnex_test_workflow" : "modelnex_test_tour";
2256
2257
  }
@@ -2300,6 +2301,51 @@ function clearActiveDraftPreview(experienceType) {
2300
2301
  } catch {
2301
2302
  }
2302
2303
  }
2304
+ function readSavedDraftPreview(storageKey) {
2305
+ if (typeof window === "undefined") return null;
2306
+ try {
2307
+ const raw = window.sessionStorage.getItem(storageKey);
2308
+ if (!raw) return null;
2309
+ const parsed = JSON.parse(raw);
2310
+ if (!parsed || typeof parsed.id !== "string") return null;
2311
+ if (parsed.experienceType !== "tour" && parsed.experienceType !== "onboarding") return null;
2312
+ return {
2313
+ id: parsed.id,
2314
+ experienceType: parsed.experienceType
2315
+ };
2316
+ } catch {
2317
+ return null;
2318
+ }
2319
+ }
2320
+ function readSuppressedDraftPreview() {
2321
+ return readSavedDraftPreview(SUPPRESSED_DRAFT_PREVIEW_STORAGE_KEY);
2322
+ }
2323
+ function persistSuppressedDraftPreview(draft) {
2324
+ if (typeof window === "undefined") return;
2325
+ try {
2326
+ window.sessionStorage.setItem(SUPPRESSED_DRAFT_PREVIEW_STORAGE_KEY, JSON.stringify(draft));
2327
+ } catch {
2328
+ }
2329
+ }
2330
+ function clearSuppressedDraftPreview(draft) {
2331
+ if (typeof window === "undefined") return;
2332
+ try {
2333
+ const suppressedDraft = readSuppressedDraftPreview();
2334
+ if (draft && suppressedDraft) {
2335
+ if (suppressedDraft.id !== draft.id || suppressedDraft.experienceType !== draft.experienceType) {
2336
+ return;
2337
+ }
2338
+ }
2339
+ window.sessionStorage.removeItem(SUPPRESSED_DRAFT_PREVIEW_STORAGE_KEY);
2340
+ } catch {
2341
+ }
2342
+ }
2343
+ function isSuppressedDraftPreview(draft) {
2344
+ const suppressedDraft = readSuppressedDraftPreview();
2345
+ return Boolean(
2346
+ suppressedDraft && suppressedDraft.id === draft.id && suppressedDraft.experienceType === draft.experienceType
2347
+ );
2348
+ }
2303
2349
  function persistPreviewSessionSuppression() {
2304
2350
  if (typeof window === "undefined") return;
2305
2351
  try {
@@ -3880,7 +3926,13 @@ function useTourPlayback({
3880
3926
  });
3881
3927
  }
3882
3928
  if (reviewModeRef.current) {
3883
- clearActiveDraftPreview(experienceType);
3929
+ if (tourRef.current?.id) {
3930
+ persistSuppressedDraftPreview({
3931
+ id: tourRef.current.id,
3932
+ experienceType
3933
+ });
3934
+ }
3935
+ clearActiveDraftPreview();
3884
3936
  }
3885
3937
  setIsActive(false);
3886
3938
  setActiveTour(null);
@@ -3929,7 +3981,13 @@ function useTourPlayback({
3929
3981
  });
3930
3982
  }
3931
3983
  if (reviewModeRef.current) {
3932
- clearActiveDraftPreview(experienceType);
3984
+ if (endingTourId) {
3985
+ persistSuppressedDraftPreview({
3986
+ id: endingTourId,
3987
+ experienceType
3988
+ });
3989
+ }
3990
+ clearActiveDraftPreview();
3933
3991
  }
3934
3992
  onTourEnd?.();
3935
3993
  }, [experienceType, userProfile, serverUrl, voice, onTourEnd, websiteId, releasePlaybackOwnership]);
@@ -3965,6 +4023,10 @@ function useTourPlayback({
3965
4023
  setIsReviewMode(shouldReview);
3966
4024
  reviewModeRef.current = shouldReview;
3967
4025
  if (shouldReview) {
4026
+ clearSuppressedDraftPreview({
4027
+ id: tour.id,
4028
+ experienceType
4029
+ });
3968
4030
  try {
3969
4031
  const previewRun = await createPreviewRun(serverUrl, toursApiBaseRef.current, tour.id, {
3970
4032
  source: "sdk_test_preview",
@@ -4005,14 +4067,23 @@ function useTourPlayback({
4005
4067
  const persistedTourId = activeDraftPreview?.experienceType === experienceType ? activeDraftPreview.id : null;
4006
4068
  const tourId = queryTourId || persistedTourId;
4007
4069
  if (!tourId) return;
4070
+ const draftPreview = { id: tourId, experienceType };
4071
+ if (!queryTourId && isSuppressedDraftPreview(draftPreview)) {
4072
+ clearActiveDraftPreview(experienceType);
4073
+ return;
4074
+ }
4008
4075
  let cancelled = false;
4009
4076
  (async () => {
4010
4077
  try {
4011
- persistActiveDraftPreview({ id: tourId, experienceType });
4078
+ if (queryTourId) {
4079
+ clearSuppressedDraftPreview(draftPreview);
4080
+ }
4081
+ persistActiveDraftPreview(draftPreview);
4012
4082
  const tour = await fetchTourById(serverUrl, toursApiBase, tourId, experienceType, websiteId);
4013
4083
  if (cancelled) return;
4014
4084
  if (!tour) {
4015
4085
  clearActiveDraftPreview(experienceType);
4086
+ persistSuppressedDraftPreview(draftPreview);
4016
4087
  console.warn("[ModelNex] Tour fetch failed:", tourId, experienceType, "(Check ModelNex server is running and CORS allows this origin)");
4017
4088
  return;
4018
4089
  }
@@ -4254,6 +4325,9 @@ function useTourPlayback({
4254
4325
 
4255
4326
  // src/hooks/useExperiencePlaybackController.ts
4256
4327
  var EXPERIENCE_PRIORITY = ["onboarding", "tour"];
4328
+ function shouldClearDraftPreviewOnPromptDismiss(prompt) {
4329
+ return Boolean(prompt?.options?.reviewMode);
4330
+ }
4257
4331
  function shouldDiscoverDraftPreview({
4258
4332
  disabled,
4259
4333
  hasPendingPrompt,
@@ -4263,6 +4337,12 @@ function shouldDiscoverDraftPreview({
4263
4337
  }) {
4264
4338
  return !disabled && !hasPendingPrompt && !isPlaybackActive && !isPreviewDiscoveryInFlight && !isStartingExperience;
4265
4339
  }
4340
+ function shouldReplayDraftPreview({
4341
+ hasExplicitQueryParam,
4342
+ isSuppressedPreview
4343
+ }) {
4344
+ return hasExplicitQueryParam || !isSuppressedPreview;
4345
+ }
4266
4346
  function shouldDiscoverEligibleTours({
4267
4347
  disabled,
4268
4348
  hasPendingPrompt,
@@ -4348,6 +4428,13 @@ function useExperiencePlaybackController({
4348
4428
  if (experienceType && prompt.experienceType !== experienceType) return;
4349
4429
  setPendingPrompt(null);
4350
4430
  pendingPromptRef.current = null;
4431
+ if (shouldClearDraftPreviewOnPromptDismiss(prompt)) {
4432
+ clearActiveDraftPreview(prompt.experienceType);
4433
+ persistSuppressedDraftPreview({
4434
+ id: prompt.tour.id,
4435
+ experienceType: prompt.experienceType
4436
+ });
4437
+ }
4351
4438
  if (!userProfile?.userId) return;
4352
4439
  void markTourDismissed(
4353
4440
  serverUrl,
@@ -4427,17 +4514,32 @@ function useExperiencePlaybackController({
4427
4514
  }
4428
4515
  const request = previewRequests[0];
4429
4516
  if (!request) return;
4517
+ const draftPreview = {
4518
+ id: request.tourId,
4519
+ experienceType: request.experienceType
4520
+ };
4521
+ if (!shouldReplayDraftPreview({
4522
+ hasExplicitQueryParam: Boolean(request.queryParam),
4523
+ isSuppressedPreview: isSuppressedDraftPreview(draftPreview)
4524
+ })) {
4525
+ clearActiveDraftPreview(request.experienceType);
4526
+ return;
4527
+ }
4430
4528
  let cancelled = false;
4431
4529
  previewDiscoveryInFlightRef.current = true;
4432
4530
  previewSessionRef.current = true;
4433
4531
  persistPreviewSessionSuppression();
4434
4532
  (async () => {
4435
4533
  try {
4436
- persistActiveDraftPreview({ id: request.tourId, experienceType: request.experienceType });
4534
+ if (request.queryParam) {
4535
+ clearSuppressedDraftPreview(draftPreview);
4536
+ }
4537
+ persistActiveDraftPreview(draftPreview);
4437
4538
  const tour = await fetchTourById(serverUrl, toursApiBase, request.tourId, request.experienceType, websiteId);
4438
4539
  if (cancelled) return;
4439
4540
  if (!tour) {
4440
4541
  clearActiveDraftPreview(request.experienceType);
4542
+ persistSuppressedDraftPreview(draftPreview);
4441
4543
  return;
4442
4544
  }
4443
4545
  if (request.queryParam) {
@@ -10931,6 +11033,13 @@ var ModelNexProvider = ({
10931
11033
  const serverUrl = serverUrlProp ?? DEFAULT_MODELNEX_SERVER_URL;
10932
11034
  const commandUrl = void 0;
10933
11035
  const disableSocket = false;
11036
+ useEffect19(() => {
11037
+ if (process.env.NODE_ENV !== "production" && !serverUrlProp) {
11038
+ console.warn(
11039
+ `[ModelNex SDK] ModelNexProvider is using the default server URL (${DEFAULT_MODELNEX_SERVER_URL}). Pass \`serverUrl\` explicitly in local development to avoid accidentally targeting the hosted backend.`
11040
+ );
11041
+ }
11042
+ }, [serverUrlProp]);
10934
11043
  const renderedChildren = children;
10935
11044
  const [activeAgentActions, setActiveAgentActions] = useState15(/* @__PURE__ */ new Set());
10936
11045
  const [stagingFields, setStagingFields] = useState15(/* @__PURE__ */ new Set());
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@modelnex/sdk",
3
- "version": "0.5.23",
3
+ "version": "0.5.25",
4
4
  "description": "React SDK for natural language control of web apps via AI agents",
5
5
  "main": "./dist/index.js",
6
6
  "module": "./dist/index.mjs",
@@ -20,15 +20,6 @@
20
20
  "dist",
21
21
  "README.md"
22
22
  ],
23
- "scripts": {
24
- "prepublishOnly": "npm run build",
25
- "build": "npm exec -- tsup src/index.ts --format cjs,esm --dts",
26
- "dev": "npm exec -- tsup src/index.ts --format cjs,esm --watch --dts",
27
- "lint": "eslint src/",
28
- "test": "npm run build && node --test tests/*.test.js",
29
- "test:ci": "npm run test && npm run test:unit",
30
- "test:unit": "node --import tsx --test tests/*.test.ts"
31
- },
32
23
  "peerDependencies": {
33
24
  "react": ">=17.0.0",
34
25
  "react-dom": ">=17.0.0",
@@ -67,5 +58,13 @@
67
58
  "bugs": {
68
59
  "url": "https://github.com/sharunaraksha/modelnex-sdk/issues"
69
60
  },
70
- "homepage": "https://github.com/sharunaraksha/modelnex-sdk#readme"
71
- }
61
+ "homepage": "https://github.com/sharunaraksha/modelnex-sdk#readme",
62
+ "scripts": {
63
+ "build": "npm exec -- tsup src/index.ts --format cjs,esm --dts",
64
+ "dev": "npm exec -- tsup src/index.ts --format cjs,esm --watch --dts",
65
+ "lint": "eslint src/",
66
+ "test": "npm run build && node --test tests/*.test.js",
67
+ "test:ci": "npm run test && npm run test:unit",
68
+ "test:unit": "node --import tsx --test tests/*.test.ts"
69
+ }
70
+ }