codex-overleaf-link 2.5.1 → 2.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.
Files changed (93) hide show
  1. package/README.md +149 -398
  2. package/README.zh-CN.md +153 -404
  3. package/extension/bootstrap/manifest.template.json +1 -1
  4. package/extension/src/background.js +78 -3
  5. package/extension/src/backgroundSharedSessions.js +55 -2
  6. package/extension/src/backgroundUpdateCoordinator.js +128 -12
  7. package/extension/src/content/activeTurnControl.js +164 -7
  8. package/extension/src/content/composerAttachments.js +11 -2
  9. package/extension/src/content/composerPanel.js +92 -12
  10. package/extension/src/content/contentRuntime.js +920 -276
  11. package/extension/src/content/contextTray.js +8 -2
  12. package/extension/src/content/contextUsageView.js +267 -0
  13. package/extension/src/content/diagnosticsPanel.js +18 -0
  14. package/extension/src/content/generated/content.bundle.js +4496 -924
  15. package/extension/src/content/generated/content.bundle.meta.json +6 -3
  16. package/extension/src/content/legacyGlobalRegistry.js +4 -0
  17. package/extension/src/content/localSkillsPanel.js +2 -2
  18. package/extension/src/content/markdownDomRenderer.js +5 -3
  19. package/extension/src/content/markdownText.js +42 -5
  20. package/extension/src/content/modelPicker.js +1 -0
  21. package/extension/src/content/nativeCompatibilityController.js +29 -10
  22. package/extension/src/content/otWarmMirror.js +1 -1
  23. package/extension/src/content/pageBridgeClient.js +2 -2
  24. package/extension/src/content/panelMaintenance.js +6 -2
  25. package/extension/src/content/panelRenderer.js +51 -27
  26. package/extension/src/content/pendingInputView.js +1 -1
  27. package/extension/src/content/projectSettingsCoordinator.js +30 -6
  28. package/extension/src/content/providerSettingsDialog.js +2 -1
  29. package/extension/src/content/recentProjects.js +4 -3
  30. package/extension/src/content/runActivitySummary.js +122 -14
  31. package/extension/src/content/runCompletionMeta.js +194 -6
  32. package/extension/src/content/runController.js +2 -1
  33. package/extension/src/content/runFailureNotice.js +8 -1
  34. package/extension/src/content/runPresence.js +18 -8
  35. package/extension/src/content/runQueueScheduler.js +9 -5
  36. package/extension/src/content/runRecoveryActions.js +2 -1
  37. package/extension/src/content/runResultActions.js +22 -20
  38. package/extension/src/content/runReviewController.js +94 -35
  39. package/extension/src/content/runTimelineView.js +224 -25
  40. package/extension/src/content/scopedPersistencePanelState.js +92 -23
  41. package/extension/src/content/sensitivePreflight.js +14 -1
  42. package/extension/src/content/sessionManager.js +49 -3
  43. package/extension/src/content/sessionMenuView.js +54 -18
  44. package/extension/src/content/sessionPersistence.js +25 -5
  45. package/extension/src/content/settingsPanel.js +15 -2
  46. package/extension/src/content/settingsWorkbench.js +127 -89
  47. package/extension/src/content/sharedSessionViewSync.js +4 -3
  48. package/extension/src/content/subagentActivityView.js +23 -7
  49. package/extension/src/content/updateNotice.js +60 -6
  50. package/extension/src/content/writebackOrchestrator.js +35 -22
  51. package/extension/src/page/compileBridge.js +193 -180
  52. package/extension/src/page/overleafCapabilities.js +4 -1
  53. package/extension/src/page/textCoordinates.js +17 -1
  54. package/extension/src/page/textFileCreator.js +9 -1
  55. package/extension/src/page/writebackRouter.js +24 -77
  56. package/extension/src/pageBridge.js +107 -66
  57. package/extension/src/popup.js +1 -1
  58. package/extension/src/shared/agentTranscript.js +350 -17
  59. package/extension/src/shared/auditRecords.js +2 -0
  60. package/extension/src/shared/compatibility.js +1 -1
  61. package/extension/src/shared/compileAdapter.js +58 -17
  62. package/extension/src/shared/composerAttachmentDraft.js +6 -1
  63. package/extension/src/shared/contextUsage.js +125 -0
  64. package/extension/src/shared/failureReasons.js +42 -3
  65. package/extension/src/shared/i18n.js +299 -29
  66. package/extension/src/shared/lineReferences.js +38 -31
  67. package/extension/src/shared/pageRpcContract.js +1 -1
  68. package/extension/src/shared/panelIcons.js +39 -0
  69. package/extension/src/shared/reviewing.js +130 -1
  70. package/extension/src/shared/runActivityModel.js +70 -4
  71. package/extension/src/shared/runExecutionSnapshot.js +13 -0
  72. package/extension/src/shared/sessionState.js +63 -4
  73. package/extension/src/shared/sessionTitle.js +14 -1
  74. package/extension/src/shared/sharedSessionBridge.js +81 -13
  75. package/extension/src/shared/staleGuard.js +72 -63
  76. package/extension/src/shared/storageDb.js +35 -3
  77. package/extension/src/shared/storageMigration.js +7 -1
  78. package/extension/src/shared/storageRunActions.js +62 -8
  79. package/extension/src/shared/undoOperations.js +126 -28
  80. package/extension/src/shared/updateRuntimeIdentity.js +16 -2
  81. package/extension/src/shared/writebackSettlement.js +21 -3
  82. package/extension/styles/panel.css +1106 -490
  83. package/native-host/src/bootstrapUpdate.js +52 -16
  84. package/native-host/src/chatBridgeRequest.js +60 -6
  85. package/native-host/src/chatBridgeResponse.js +10 -1
  86. package/native-host/src/chatCompletionsBridge.js +29 -2
  87. package/native-host/src/codexPromptAssembly.js +86 -118
  88. package/native-host/src/codexSessionRunner.js +126 -17
  89. package/native-host/src/providerContextError.js +20 -0
  90. package/native-host/src/taskRunnerRuntime.js +18 -6
  91. package/native-host/src/textPatch.js +473 -377
  92. package/native-host/src/updateManager.js +120 -15
  93. package/package.json +5 -8
@@ -35,6 +35,11 @@ const ACTIVATION_DELAY_MS = 125;
35
35
  const APPLYING_TRANSACTION_STATES = new Set(['activation_pending', 'applying', 'awaiting_health',
36
36
  ...BootstrapUpdate.BOOTSTRAP_STATES]);
37
37
  const CONSENT_MIGRATION_TARGET_VERSION = '1.9.4';
38
+ // Pure reads of user-owned stores that do not depend on update files. They stay
39
+ // available while a migration or its recovery is unsettled.
40
+ const READ_ONLY_DURING_UPDATE = new Set(['codex.providers.list']);
41
+ // A user-requested return to the source version of an applied update.
42
+ const USER_ROLLBACK_REASON = 'update_user_rollback';
38
43
  const UPDATE_METHODS = new Set([
39
44
  'update.status',
40
45
  'update.bootstrap',
@@ -107,7 +112,12 @@ async function handleUpdateRequest(request, options = {}) {
107
112
  case 'update.confirm':
108
113
  return okResponse(request.id, await withUpdateMutex(context, () => confirmUpdate(context, request.params || {})));
109
114
  case 'update.rollback':
110
- return okResponse(request.id, await withUpdateMutex(context, () => rollbackUpdate(context, request.params || {})));
115
+ return okResponse(request.id, await withUpdateMutex(context, () => {
116
+ if (request.params?.reasonCode === USER_ROLLBACK_REASON && !getApplyGate(options).idle) {
117
+ throw updateError('update_native_busy', 'Native host is still processing a Codex task.');
118
+ }
119
+ return rollbackUpdate(context, request.params || {});
120
+ }));
111
121
  case 'update.cancel':
112
122
  return okResponse(request.id, await cancelNetworkUpdate(context, request.params || {}));
113
123
  case 'update.revoke': {
@@ -264,7 +274,8 @@ async function recoverBootstrapTransaction(options, transactionId, token) {
264
274
  }
265
275
 
266
276
  function assertBootstrapBusinessAvailable(request, options = {}) {
267
- if (isUpdateMethod(request?.method) || ['bridge.ping', 'codex.cancel'].includes(request?.method)) return;
277
+ if (isUpdateMethod(request?.method) || ['bridge.ping', 'codex.cancel'].includes(request?.method)
278
+ || READ_ONLY_DURING_UPDATE.has(request?.method)) return;
268
279
  const context = getManagedContext(options);
269
280
  if (!context.managed) return;
270
281
  const journal = readJournal(context);
@@ -352,9 +363,14 @@ async function checkForUpdate(context, params = {}, options = {}) {
352
363
  };
353
364
  }
354
365
  const cooldowns = readJsonSafe(path.join(context.updatesRoot, COOLDOWN_FILE), {});
355
- if (Number(cooldowns[latestVersion]?.until || 0) > Date.now()) {
366
+ const cooldown = cooldowns[latestVersion];
367
+ // Read legacy user-rollback cooldowns, allowing explicit checks to bypass
368
+ // them. New user rollbacks defer browser prompts without a Native cooldown.
369
+ if (Number(cooldown?.until || 0) > Date.now()
370
+ && !(cooldown.reasonCode === USER_ROLLBACK_REASON && params.manual === true)) {
356
371
  return { managed: true, available: false, reason: 'cooldown', currentVersion, latestVersion,
357
- cooldownUntil: Number(cooldowns[latestVersion].until), etag: releaseResponse.headers.get('etag') || '' };
372
+ cooldownUntil: Number(cooldown.until), cooldownReason: cooldown.reasonCode || '',
373
+ etag: releaseResponse.headers.get('etag') || '' };
358
374
  }
359
375
 
360
376
  const manifestBytes = await fetchReleaseAsset(
@@ -393,6 +409,19 @@ async function checkForUpdate(context, params = {}, options = {}) {
393
409
  });
394
410
  return manual;
395
411
  }
412
+ try {
413
+ BootstrapUpdate.assertMigrationSource(context, manifest, currentVersion);
414
+ } catch (error) {
415
+ if (error.code !== 'update_bootstrap_source_mismatch') throw error;
416
+ // Never cached as an installable candidate: only a reinstall can replace
417
+ // startup files this release cannot migrate.
418
+ const manual = { managed: true, available: false, reason: 'manual_install_required', code: error.code,
419
+ currentVersion, latestVersion, manualInstall: {}, etag: releaseResponse.headers.get('etag') || '' };
420
+ atomicWriteJson(path.join(context.updatesRoot, MANUAL_CANDIDATE_FILE), { ...manual,
421
+ checkedAt: new Date().toISOString(), manifestBase64: manifestBytes.toString('base64'),
422
+ signatureBase64: signatureBytes.toString('base64') });
423
+ return manual;
424
+ }
396
425
  const candidate = {
397
426
  checkedAt: new Date().toISOString(),
398
427
  currentVersion,
@@ -416,8 +445,15 @@ function readManualCandidate(context, currentVersion, etag) {
416
445
  Buffer.from(manual.signatureBase64, 'base64'),
417
446
  { allowBootstrapMismatch: true }
418
447
  );
419
- if (!isNewerStableVersion(manifest.version, currentVersion)
420
- || manifest.bootstrapProtocol === BOOTSTRAP_PROTOCOL) return null;
448
+ if (!isNewerStableVersion(manifest.version, currentVersion)) return null;
449
+ if (manifest.bootstrapProtocol === BOOTSTRAP_PROTOCOL) {
450
+ // Same protocol: a hint only while the installed startup files still
451
+ // cannot be migrated by this signed release.
452
+ try { BootstrapUpdate.assertMigrationSource(context, manifest, currentVersion); return null; }
453
+ catch (error) { if (error.code !== 'update_bootstrap_source_mismatch') return null; }
454
+ return { managed: true, available: false, reason: 'manual_install_required', code: 'update_bootstrap_source_mismatch',
455
+ currentVersion, latestVersion: manifest.version, manualInstall: {}, etag };
456
+ }
421
457
  return {
422
458
  managed: true, available: false, reason: 'manual_install_required',
423
459
  currentVersion, latestVersion: manifest.version,
@@ -494,6 +530,7 @@ function authorizeUpdate(context, params = {}) {
494
530
  !isNewerStableVersion(targetVersion, activeVersion)) {
495
531
  throw updateError('update_consent_mismatch', 'Update authorization does not match the active signed candidate.');
496
532
  }
533
+ BootstrapUpdate.assertMigrationSource(context, manifest, activeVersion);
497
534
  const existing = readAuthorization(context);
498
535
  if (existing?.id === authorizationId &&
499
536
  existing.targetVersion === targetVersion &&
@@ -536,6 +573,11 @@ async function stageCandidate(context, options = {}) {
536
573
  throw updateError('update_candidate_stale', 'The staged update is no longer newer than the active version.');
537
574
  }
538
575
  const authorization = requireAuthorization(context, manifest.version, ['authorized', 'bound']);
576
+ try { BootstrapUpdate.assertMigrationSource(context, manifest, activeVersion); }
577
+ catch (error) {
578
+ if (authorization.state === 'authorized') settleAuthorization(context, authorization.id, 'revoked');
579
+ throw error;
580
+ }
539
581
  const existingJournal = readJournal(context);
540
582
  if (existingJournal?.state === 'staged' && existingJournal.targetVersion === manifest.version) {
541
583
  if (existingJournal.authorizationId !== authorization.id ||
@@ -832,14 +874,40 @@ function rollbackUpdate(context, params = {}) {
832
874
  if (journal.bootstrapMigration && !params.transactionId && journal.bootstrapMigration.phase !== 'pending') {
833
875
  throw updateError('update_transaction_mismatch', 'Bootstrap rollback must identify its update transaction.');
834
876
  }
877
+ const userRequested = params.reasonCode === USER_ROLLBACK_REASON;
878
+ // Check every precondition before writing the rolling-back state, so an
879
+ // unavailable request never turns a working installation into a recovery.
880
+ if (userRequested && (!params.transactionId
881
+ || (journal.state === 'committed' && !userRollbackAvailable(context, journal)))) {
882
+ throw updateError('update_rollback_unavailable', 'The previous version is no longer available for rollback.');
883
+ }
835
884
  const migration = Boolean(journal.bootstrapMigration);
836
885
  if (migration) writeJournal(context, { ...journal, state: 'bootstrap_rolling_back' });
837
- const rollbackEvidence = rollbackFiles(context, journal, { deferSourceActivation: migration });
886
+ let rollbackEvidence;
887
+ try {
888
+ rollbackEvidence = rollbackFiles(context, journal, { deferSourceActivation: migration });
889
+ } catch (error) {
890
+ // The incomplete state remains. The failure record bounds automatic retries
891
+ // and lets status report the reason; explicit rollback requests still run.
892
+ const failed = readJournal(context);
893
+ if (failed?.id === journal.id) {
894
+ writeJournal(context, { ...failed, updatedAt: new Date().toISOString(), rollbackFailure: {
895
+ code: safeErrorCode(error), attempts: Number(failed.rollbackFailure?.attempts || 0) + 1,
896
+ at: new Date().toISOString() } });
897
+ }
898
+ throw error;
899
+ }
838
900
  const cooldowns = readJsonSafe(path.join(context.updatesRoot, COOLDOWN_FILE), {});
839
- cooldowns[journal.targetVersion] = {
840
- until: Date.now() + 24 * 60 * 60 * 1000,
841
- reasonCode: normalizeReasonCode(params.reasonCode)
842
- };
901
+ if (userRequested) {
902
+ // The restored Native may predate manual-check exemptions. Keep its
903
+ // original check usable; the coordinator snoozes automatic prompts.
904
+ delete cooldowns[journal.targetVersion];
905
+ } else {
906
+ cooldowns[journal.targetVersion] = {
907
+ until: Date.now() + 24 * 60 * 60 * 1000,
908
+ reasonCode: normalizeReasonCode(params.reasonCode)
909
+ };
910
+ }
843
911
  atomicWriteJson(path.join(context.updatesRoot, COOLDOWN_FILE), cooldowns);
844
912
  const restoredJournal = {
845
913
  ...journal,
@@ -848,6 +916,7 @@ function rollbackUpdate(context, params = {}) {
848
916
  rolledBackAt: new Date().toISOString(),
849
917
  updatedAt: new Date().toISOString(),
850
918
  previousManifest: undefined,
919
+ rollbackFailure: undefined,
851
920
  rollbackEvidence,
852
921
  ...(migration ? { bootstrapMigration: { ...journal.bootstrapMigration, phase: 'restored',
853
922
  sourceUnchanged: rollbackEvidence.bootstrapSourceUnchanged === true },
@@ -867,6 +936,27 @@ function rollbackUpdate(context, params = {}) {
867
936
  return { transactionId: journal.id, version: journal.sourceVersion, state: 'rolled_back', restoredPair: rollbackEvidence };
868
937
  }
869
938
 
939
+ // A committed update can be returned to its source pair only while every
940
+ // source layer is still on disk: the previous runtime slot, the source Native
941
+ // version, the saved manifest and, for a Bootstrap migration, its backups.
942
+ function userRollbackAvailable(context, journal) {
943
+ if (journal?.state !== 'committed' || !parseSemver(journal.sourceVersion) || !journal.previousManifest
944
+ || readVersionPointer(context.nativeRoot, 'active-version') !== journal.targetVersion) return false;
945
+ if (journal.bootstrapMigration
946
+ && !fs.existsSync(path.join(context.updatesRoot, 'bootstrap-migrations', journal.id, 'state.json'))) return false;
947
+ // Status runs often, so this reads only the version identities. rollbackFiles
948
+ // still verifies both full source trees before and after restoring them.
949
+ const previousRuntime = path.join(context.extensionRoot, 'slots', 'previous', 'runtime');
950
+ try {
951
+ const compatibility = fs.readFileSync(path.join(previousRuntime, 'src', 'shared', 'compatibility.js'), 'utf8');
952
+ const nativePackage = readJsonSafe(path.join(context.nativeRoot, 'versions', journal.sourceVersion, 'package.json'), null);
953
+ return compatibility.match(/BUILD_TARGET_VERSION\s*=\s*['"]([^'"]+)['"]/)?.[1] === journal.sourceVersion
954
+ && nativePackage?.version === journal.sourceVersion;
955
+ } catch (_) {
956
+ return false;
957
+ }
958
+ }
959
+
870
960
  function revokeUpdate(context, params = {}) {
871
961
  const authorizationId = String(params.authorizationId || '');
872
962
  const targetVersion = String(params.targetVersion || '');
@@ -1003,7 +1093,12 @@ function recoverAndReadStatus(context, params = {}) {
1003
1093
  }
1004
1094
  journal = BootstrapUpdate.bindBootstrapMigration(context, journal, bootstrapDependencies(context));
1005
1095
  if (BootstrapUpdate.bootstrapExpired(journal, { recoveryRequested: params.bootstrapRecovery === true, context })) {
1006
- rollbackUpdate(context, { transactionId: journal.id, expectedState: journal.state, reasonCode: 'update_bootstrap_health_timeout' });
1096
+ try {
1097
+ rollbackUpdate(context, { transactionId: journal.id, expectedState: journal.state, reasonCode: 'update_bootstrap_health_timeout' });
1098
+ } catch (_error) {
1099
+ // Recorded as rollbackFailure on the journal; status still answers so
1100
+ // the browser can show the reason instead of an unreadable state.
1101
+ }
1007
1102
  journal = readJournal(context);
1008
1103
  }
1009
1104
  if (journal?.state === 'rolled_back' || journal?.state === 'superseded') {
@@ -1035,7 +1130,11 @@ function finishSourceActivation(context, journal) {
1035
1130
  if (!sameRuntimeEvidence(actual, journal.rollbackEvidence)) {
1036
1131
  throw updateError('update_rollback_unconfirmed', 'The recovered source files changed before activation.');
1037
1132
  }
1038
- BootstrapUpdate.bootstrapEvidence(context, journal, 'source');
1133
+ const bootstrap = BootstrapUpdate.bootstrapEvidence(context, journal, 'source');
1134
+ if (journal.bootstrapMigration
1135
+ && (!bootstrap?.digest || bootstrap.digest !== journal.rollbackEvidence?.bootstrapSha256)) {
1136
+ throw updateError('update_bootstrap_source_mismatch', 'The recovered Bootstrap bytes changed before activation.');
1137
+ }
1039
1138
  settleAuthorization(context, journal.authorizationId, 'revoked');
1040
1139
  atomicWriteText(path.join(context.nativeRoot, 'active-version'), journal.sourceVersion + '\n');
1041
1140
  const finished = { ...journal, sourceActivationPending: false };
@@ -1059,10 +1158,13 @@ function settleReinstalledUpdate(context, version, expected) {
1059
1158
  }
1060
1159
  const reinstalledPair = readManagedRuntimeEvidence(context, version);
1061
1160
  const timestamp = new Date().toISOString();
1161
+ // The installer already activated `version`; a pending switch back to the
1162
+ // source would block business requests and could repoint the launcher.
1062
1163
  writeJournal(context, {
1063
1164
  ...journal, state: 'superseded', supersededFromState: journal.state,
1064
1165
  supersededByVersion: version, supersededAt: timestamp,
1065
- updatedAt: timestamp, reasonCode: 'update_reinstalled', reinstalledPair
1166
+ updatedAt: timestamp, reasonCode: 'update_reinstalled', reinstalledPair,
1167
+ sourceActivationPending: false
1066
1168
  });
1067
1169
  const authorization = readAuthorization(context);
1068
1170
  if (authorization?.state === 'bound' && authorization.id === journal.authorizationId
@@ -1104,6 +1206,8 @@ function currentStatus(context, journal = readJournal(context)) {
1104
1206
  ...(journal.state === 'awaiting_health' && BootstrapUpdate.migrationHint(context, journal)
1105
1207
  && journal.bootstrapMigration?.phase !== 'loaded' ? { state: 'bootstrap_pending' } : {}) } : null,
1106
1208
  authorization: publicAuthorization(readAuthorization(context)),
1209
+ rollbackAvailable: userRollbackAvailable(context, journal)
1210
+ ? { transactionId: journal.id, sourceVersion: journal.sourceVersion, targetVersion: journal.targetVersion } : null,
1107
1211
  restoredPair,
1108
1212
  reinstalledPair
1109
1213
  };
@@ -1347,7 +1451,8 @@ function publicTransaction(journal) {
1347
1451
  supersededAt: journal.supersededAt,
1348
1452
  supersededByVersion: journal.supersededByVersion,
1349
1453
  supersededFromState: journal.supersededFromState,
1350
- reasonCode: journal.reasonCode || ''
1454
+ reasonCode: journal.reasonCode || '',
1455
+ rollbackFailureCode: journal.rollbackFailure?.code || ''
1351
1456
  };
1352
1457
  }
1353
1458
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "codex-overleaf-link",
3
- "version": "2.5.1",
3
+ "version": "2.6.0",
4
4
  "description": "Cross-platform Chrome bridge that connects Codex to the active Overleaf project.",
5
5
  "license": "MIT",
6
6
  "type": "commonjs",
@@ -16,13 +16,9 @@
16
16
  "agent:codex": "node scripts/codex-json-agent.mjs",
17
17
  "build:content": "node scripts/build-content-bundle.mjs",
18
18
  "check:module-graph": "node scripts/check-content-module-graph.mjs",
19
- "test": "npm run check:module-graph && node scripts/run-tests.mjs",
20
- "test:fast": "npm run check:module-graph && node scripts/run-tests.mjs --fast",
21
- "test:focus": "npm run check:module-graph && node scripts/run-tests.mjs",
22
- "check:architecture": "node scripts/check-architecture-budget.mjs --enforce-target",
23
- "benchmark:large": "node scripts/benchmark-large-project.mjs --gate",
19
+ "test": "npm run build:content && node scripts/run-tests.mjs",
24
20
  "smoke:extension": "npm run build:content && node scripts/smoke-extension.mjs",
25
- "smoke:prepare:chrome": "npm run build:content && npm run test:fast && npm run install:managed",
21
+ "smoke:prepare:chrome": "npm test && npm run install:managed",
26
22
  "verify:release": "node scripts/verify-release.mjs",
27
23
  "verify:source": "node scripts/verify-release.mjs --source-hygiene-only",
28
24
  "verify:release-artifacts": "node scripts/verify-release-artifacts.mjs",
@@ -38,7 +34,8 @@
38
34
  "uninstall:native": "node scripts/uninstall-native-host.mjs",
39
35
  "bridge": "node native-host/src/index.js",
40
36
  "verify:npm-package": "node scripts/verify-npm-package.mjs",
41
- "pack:npm": "npm run build:content && node scripts/verify-npm-package.mjs --pack"
37
+ "pack:npm": "npm run build:content && node scripts/verify-npm-package.mjs --pack",
38
+ "test:platform": "npm run build:content && node scripts/run-tests.mjs --platform"
42
39
  },
43
40
  "engines": {
44
41
  "node": ">=20"