@cerefox/memory 1.18.0 → 1.19.1

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 (37) hide show
  1. package/AGENT_GUIDE.md +8 -3
  2. package/AGENT_QUICK_REFERENCE.md +3 -3
  3. package/dist/bin/cerefox.js +847 -659
  4. package/dist/frontend/assets/index-01k5C3sG.css +1 -0
  5. package/dist/frontend/assets/index-HNs8rgY1.js +121 -0
  6. package/dist/frontend/assets/index-HNs8rgY1.js.map +1 -0
  7. package/dist/frontend/index.html +2 -2
  8. package/dist/server-assets/_shared/ef-meta/index.ts +3 -3
  9. package/dist/server-assets/_shared/embeddings/onnx-embedder.ts +25 -9
  10. package/dist/server-assets/_shared/mcp-tools/_document-meta.ts +24 -2
  11. package/dist/server-assets/_shared/mcp-tools/_utils.ts +92 -0
  12. package/dist/server-assets/_shared/mcp-tools/audit-log.ts +6 -2
  13. package/dist/server-assets/_shared/mcp-tools/audit-ops.ts +27 -0
  14. package/dist/server-assets/_shared/mcp-tools/get-document.ts +11 -1
  15. package/dist/server-assets/_shared/mcp-tools/get-help-content.ts +3 -3
  16. package/dist/server-assets/_shared/mcp-tools/ingest.ts +55 -17
  17. package/dist/server-assets/_shared/mcp-tools/metadata-search.ts +3 -1
  18. package/dist/server-assets/_shared/mcp-tools/partial-edits.ts +8 -13
  19. package/dist/server-assets/_shared/mcp-tools/search.ts +11 -2
  20. package/dist/server-assets/_shared/mcp-tools/set-document-metadata.ts +31 -5
  21. package/dist/server-assets/db/migrations/0035_drop_content_hash_unique.sql +21 -0
  22. package/dist/server-assets/db/migrations/0036_metadata_revision.sql +43 -0
  23. package/dist/server-assets/db/rpcs.sql +252 -53
  24. package/dist/server-assets/db/schema.sql +41 -4
  25. package/dist/server-assets/supabase/functions/cerefox-get-document/index.ts +8 -1
  26. package/dist/server-assets/supabase/functions/cerefox-ingest/index.ts +42 -6
  27. package/docs/api/openapi.json +67 -1
  28. package/docs/guides/cli.md +3 -1
  29. package/docs/guides/configuration.md +1 -1
  30. package/docs/guides/connect-agents.md +18 -7
  31. package/docs/guides/ops-scripts.md +1 -1
  32. package/docs/guides/setup-local.md +30 -6
  33. package/docs/guides/upgrading.md +14 -0
  34. package/package.json +6 -6
  35. package/dist/frontend/assets/index-B0H_N4if.css +0 -1
  36. package/dist/frontend/assets/index-CxMT7hjH.js +0 -121
  37. package/dist/frontend/assets/index-CxMT7hjH.js.map +0 -1
@@ -5318,7 +5318,7 @@ var init_cli_core = __esm(() => {
5318
5318
  });
5319
5319
 
5320
5320
  // src/meta.ts
5321
- var PKG_VERSION = "1.18.0";
5321
+ var PKG_VERSION = "1.19.1";
5322
5322
  var init_meta = () => {};
5323
5323
 
5324
5324
  // ../../_shared/config/paths.ts
@@ -5436,7 +5436,7 @@ var init_config = __esm(() => {
5436
5436
  init_env();
5437
5437
  });
5438
5438
 
5439
- // ../../node_modules/.bun/@supabase+supabase-js@2.117.2/node_modules/@supabase/supabase-js/dist/tracingRegistry.mjs
5439
+ // ../../node_modules/.bun/@supabase+supabase-js@2.117.3/node_modules/@supabase/supabase-js/dist/tracingRegistry.mjs
5440
5440
  function getTraceContextExtractor() {
5441
5441
  return globalThis[EXTRACTOR_KEY];
5442
5442
  }
@@ -6050,7 +6050,7 @@ var require_tslib = __commonJS(function(exports, module) {
6050
6050
  });
6051
6051
  });
6052
6052
 
6053
- // ../../node_modules/.bun/@supabase+functions-js@2.117.2/node_modules/@supabase/functions-js/dist/main/helper.js
6053
+ // ../../node_modules/.bun/@supabase+functions-js@2.117.3/node_modules/@supabase/functions-js/dist/main/helper.js
6054
6054
  var require_helper = __commonJS(function(exports) {
6055
6055
  Object.defineProperty(exports, "__esModule", { value: true });
6056
6056
  exports.resolveFetch = undefined;
@@ -6063,7 +6063,7 @@ var require_helper = __commonJS(function(exports) {
6063
6063
  exports.resolveFetch = resolveFetch;
6064
6064
  });
6065
6065
 
6066
- // ../../node_modules/.bun/@supabase+functions-js@2.117.2/node_modules/@supabase/functions-js/dist/main/types.js
6066
+ // ../../node_modules/.bun/@supabase+functions-js@2.117.3/node_modules/@supabase/functions-js/dist/main/types.js
6067
6067
  var require_types = __commonJS(function(exports) {
6068
6068
  Object.defineProperty(exports, "__esModule", { value: true });
6069
6069
  exports.FunctionRegion = exports.FunctionsHttpError = exports.FunctionsRelayError = exports.FunctionsFetchError = exports.FunctionsError = undefined;
@@ -6124,7 +6124,7 @@ var require_types = __commonJS(function(exports) {
6124
6124
  })(FunctionRegion || (exports.FunctionRegion = FunctionRegion = {}));
6125
6125
  });
6126
6126
 
6127
- // ../../node_modules/.bun/@supabase+functions-js@2.117.2/node_modules/@supabase/functions-js/dist/main/FunctionsClient.js
6127
+ // ../../node_modules/.bun/@supabase+functions-js@2.117.3/node_modules/@supabase/functions-js/dist/main/FunctionsClient.js
6128
6128
  var require_FunctionsClient = __commonJS(function(exports) {
6129
6129
  Object.defineProperty(exports, "__esModule", { value: true });
6130
6130
  exports.FunctionsClient = undefined;
@@ -6164,7 +6164,7 @@ var require_FunctionsClient = __commonJS(function(exports) {
6164
6164
  const hasContentTypeHeader = !!headers && Object.keys(headers).some((key) => key.toLowerCase() === "content-type");
6165
6165
  if (functionArgs && !hasContentTypeHeader) {
6166
6166
  if (typeof Blob !== "undefined" && functionArgs instanceof Blob || functionArgs instanceof ArrayBuffer) {
6167
- _headers["Content-Type"] = "application/octet-stream";
6167
+ _headers["Content-Type"] = functionArgs.type || "application/octet-stream";
6168
6168
  body = functionArgs;
6169
6169
  } else if (typeof functionArgs === "string") {
6170
6170
  _headers["Content-Type"] = "text/plain";
@@ -6243,7 +6243,7 @@ var require_FunctionsClient = __commonJS(function(exports) {
6243
6243
  exports.FunctionsClient = FunctionsClient;
6244
6244
  });
6245
6245
 
6246
- // ../../node_modules/.bun/@supabase+functions-js@2.117.2/node_modules/@supabase/functions-js/dist/main/index.js
6246
+ // ../../node_modules/.bun/@supabase+functions-js@2.117.3/node_modules/@supabase/functions-js/dist/main/index.js
6247
6247
  var require_main = __commonJS(function(exports) {
6248
6248
  Object.defineProperty(exports, "__esModule", { value: true });
6249
6249
  exports.FunctionRegion = exports.FunctionsRelayError = exports.FunctionsHttpError = exports.FunctionsFetchError = exports.FunctionsError = exports.FunctionsClient = undefined;
@@ -6269,7 +6269,7 @@ var require_main = __commonJS(function(exports) {
6269
6269
  } });
6270
6270
  });
6271
6271
 
6272
- // ../../node_modules/.bun/@supabase+postgrest-js@2.117.2/node_modules/@supabase/postgrest-js/dist/index.mjs
6272
+ // ../../node_modules/.bun/@supabase+postgrest-js@2.117.3/node_modules/@supabase/postgrest-js/dist/index.mjs
6273
6273
  function _typeof(o) {
6274
6274
  "@babel/helpers - typeof";
6275
6275
  return _typeof = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(o$1) {
@@ -6364,7 +6364,8 @@ async function fetchWithRetry(fetchImpl, url, request, retryEnabled) {
6364
6364
  signal: request.signal
6365
6365
  });
6366
6366
  } catch (fetchError) {
6367
- if ((fetchError === null || fetchError === undefined ? undefined : fetchError.name) === "AbortError" || (fetchError === null || fetchError === undefined ? undefined : fetchError.code) === "ABORT_ERR")
6367
+ var _request$signal;
6368
+ if ((fetchError === null || fetchError === undefined ? undefined : fetchError.name) === "AbortError" || (fetchError === null || fetchError === undefined ? undefined : fetchError.code) === "ABORT_ERR" || (fetchError === null || fetchError === undefined ? undefined : fetchError.name) === "TimeoutError" && ((_request$signal = request.signal) === null || _request$signal === undefined ? undefined : _request$signal.aborted))
6368
6369
  throw fetchError;
6369
6370
  if (!RETRYABLE_METHODS.includes(request.method))
6370
6371
  throw fetchError;
@@ -7179,7 +7180,7 @@ var init_dist = __esm(() => {
7179
7180
  };
7180
7181
  });
7181
7182
 
7182
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/lib/websocket-factory.js
7183
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/lib/websocket-factory.js
7183
7184
  var require_websocket_factory = __commonJS(function(exports) {
7184
7185
  Object.defineProperty(exports, "__esModule", { value: true });
7185
7186
  exports.WebSocketFactory = undefined;
@@ -7256,14 +7257,14 @@ Suggested solution: ${env.workaround}`;
7256
7257
  exports.default = WebSocketFactory;
7257
7258
  });
7258
7259
 
7259
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/lib/version.js
7260
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/lib/version.js
7260
7261
  var require_version = __commonJS(function(exports) {
7261
7262
  Object.defineProperty(exports, "__esModule", { value: true });
7262
7263
  exports.version = undefined;
7263
- exports.version = "2.117.2";
7264
+ exports.version = "2.117.3";
7264
7265
  });
7265
7266
 
7266
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/lib/constants.js
7267
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/lib/constants.js
7267
7268
  var require_constants = __commonJS(function(exports) {
7268
7269
  Object.defineProperty(exports, "__esModule", { value: true });
7269
7270
  exports.CONNECTION_STATE = exports.TRANSPORTS = exports.CHANNEL_EVENTS = exports.CHANNEL_STATES = exports.SOCKET_STATES = exports.MAX_PUSH_BUFFER_SIZE = exports.WS_CLOSE_NORMAL = exports.POSTGRES_CHANGES_WAIT_ERROR_GRACE = exports.DEFAULT_POSTGRES_CHANGES_WAIT_TIMEOUT = exports.DEFAULT_TIMEOUT = exports.VERSION = exports.DEFAULT_VSN = exports.VSN_2_0_0 = exports.VSN_1_0_0 = exports.DEFAULT_VERSION = undefined;
@@ -7310,7 +7311,7 @@ var require_constants = __commonJS(function(exports) {
7310
7311
  };
7311
7312
  });
7312
7313
 
7313
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/lib/serializer.js
7314
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/lib/serializer.js
7314
7315
  var require_serializer = __commonJS(function(exports) {
7315
7316
  Object.defineProperty(exports, "__esModule", { value: true });
7316
7317
 
@@ -7462,7 +7463,7 @@ var require_serializer = __commonJS(function(exports) {
7462
7463
  exports.default = Serializer;
7463
7464
  });
7464
7465
 
7465
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/lib/transformers.js
7466
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/lib/transformers.js
7466
7467
  var require_transformers = __commonJS(function(exports) {
7467
7468
  Object.defineProperty(exports, "__esModule", { value: true });
7468
7469
  exports.httpEndpointURL = exports.toTimestampString = exports.toArray = exports.toJson = exports.toNumber = exports.toBoolean = exports.convertCell = exports.convertColumn = exports.convertChangeData = exports.PostgresTypes = undefined;
@@ -9047,7 +9048,7 @@ var require_phoenix_cjs = __commonJS(function(exports, module) {
9047
9048
  };
9048
9049
  });
9049
9050
 
9050
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/phoenix/presenceAdapter.js
9051
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/phoenix/presenceAdapter.js
9051
9052
  var require_presenceAdapter = __commonJS(function(exports) {
9052
9053
  Object.defineProperty(exports, "__esModule", { value: true });
9053
9054
  var phoenix_1 = require_phoenix_cjs();
@@ -9122,7 +9123,7 @@ var require_presenceAdapter = __commonJS(function(exports) {
9122
9123
  }
9123
9124
  });
9124
9125
 
9125
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/RealtimePresence.js
9126
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/RealtimePresence.js
9126
9127
  var require_RealtimePresence = __commonJS(function(exports) {
9127
9128
  Object.defineProperty(exports, "__esModule", { value: true });
9128
9129
  exports.REALTIME_PRESENCE_LISTEN_EVENTS = undefined;
@@ -9147,7 +9148,7 @@ var require_RealtimePresence = __commonJS(function(exports) {
9147
9148
  exports.default = RealtimePresence;
9148
9149
  });
9149
9150
 
9150
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/lib/normalizeChannelError.js
9151
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/lib/normalizeChannelError.js
9151
9152
  var require_normalizeChannelError = __commonJS(function(exports) {
9152
9153
  Object.defineProperty(exports, "__esModule", { value: true });
9153
9154
  exports.normalizeChannelError = normalizeChannelError;
@@ -9170,7 +9171,7 @@ var require_normalizeChannelError = __commonJS(function(exports) {
9170
9171
  }
9171
9172
  });
9172
9173
 
9173
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/phoenix/channelAdapter.js
9174
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/phoenix/channelAdapter.js
9174
9175
  var require_channelAdapter = __commonJS(function(exports) {
9175
9176
  Object.defineProperty(exports, "__esModule", { value: true });
9176
9177
  var constants_1 = require_constants();
@@ -9272,7 +9273,7 @@ var require_channelAdapter = __commonJS(function(exports) {
9272
9273
  }
9273
9274
  });
9274
9275
 
9275
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/RealtimePostgresFilterBuilder.js
9276
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/RealtimePostgresFilterBuilder.js
9276
9277
  var require_RealtimePostgresFilterBuilder = __commonJS(function(exports) {
9277
9278
  Object.defineProperty(exports, "__esModule", { value: true });
9278
9279
  exports.postgresChangesFilter = exports.RealtimePostgresFilterBuilder = undefined;
@@ -9362,7 +9363,7 @@ var require_RealtimePostgresFilterBuilder = __commonJS(function(exports) {
9362
9363
  exports.postgresChangesFilter = postgresChangesFilter;
9363
9364
  });
9364
9365
 
9365
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/RealtimeChannel.js
9366
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/RealtimeChannel.js
9366
9367
  var require_RealtimeChannel = __commonJS(function(exports) {
9367
9368
  Object.defineProperty(exports, "__esModule", { value: true });
9368
9369
  exports.REALTIME_CHANNEL_STATES = exports.REALTIME_SUBSCRIBE_STATES = exports.REALTIME_LISTEN_TYPES = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = exports.postgresChangesFilter = exports.RealtimePostgresFilterBuilder = undefined;
@@ -9767,7 +9768,7 @@ var require_RealtimeChannel = __commonJS(function(exports) {
9767
9768
  exports.default = RealtimeChannel;
9768
9769
  });
9769
9770
 
9770
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/phoenix/socketAdapter.js
9771
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/phoenix/socketAdapter.js
9771
9772
  var require_socketAdapter = __commonJS(function(exports) {
9772
9773
  Object.defineProperty(exports, "__esModule", { value: true });
9773
9774
  var phoenix_1 = require_phoenix_cjs();
@@ -9883,7 +9884,7 @@ var require_socketAdapter = __commonJS(function(exports) {
9883
9884
  exports.default = SocketAdapter;
9884
9885
  });
9885
9886
 
9886
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/RealtimeClient.js
9887
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/RealtimeClient.js
9887
9888
  var require_RealtimeClient = __commonJS(function(exports) {
9888
9889
  Object.defineProperty(exports, "__esModule", { value: true });
9889
9890
  var tslib_1 = require_tslib();
@@ -10349,7 +10350,7 @@ var require_RealtimeClient = __commonJS(function(exports) {
10349
10350
  exports.default = RealtimeClient;
10350
10351
  });
10351
10352
 
10352
- // ../../node_modules/.bun/@supabase+realtime-js@2.117.2/node_modules/@supabase/realtime-js/dist/main/index.js
10353
+ // ../../node_modules/.bun/@supabase+realtime-js@2.117.3/node_modules/@supabase/realtime-js/dist/main/index.js
10353
10354
  var require_main2 = __commonJS(function(exports) {
10354
10355
  Object.defineProperty(exports, "__esModule", { value: true });
10355
10356
  exports.WebSocketFactory = exports.REALTIME_CHANNEL_STATES = exports.REALTIME_SUBSCRIBE_STATES = exports.REALTIME_PRESENCE_LISTEN_EVENTS = exports.REALTIME_POSTGRES_CHANGES_LISTEN_EVENT = exports.REALTIME_LISTEN_TYPES = exports.postgresChangesFilter = exports.RealtimePostgresFilterBuilder = exports.RealtimeClient = exports.RealtimeChannel = exports.RealtimePresence = undefined;
@@ -10685,7 +10686,7 @@ var init_dist2 = __esm(() => {
10685
10686
  };
10686
10687
  });
10687
10688
 
10688
- // ../../node_modules/.bun/@supabase+storage-js@2.117.2/node_modules/@supabase/storage-js/dist/index.mjs
10689
+ // ../../node_modules/.bun/@supabase+storage-js@2.117.3/node_modules/@supabase/storage-js/dist/index.mjs
10689
10690
  function _typeof2(o) {
10690
10691
  "@babel/helpers - typeof";
10691
10692
  return _typeof2 = typeof Symbol == "function" && typeof Symbol.iterator == "symbol" ? function(o$1) {
@@ -10993,7 +10994,7 @@ var StorageError, StorageApiError, StorageUnknownError, resolveFetch = (customFe
10993
10994
  throw error;
10994
10995
  }
10995
10996
  }
10996
- }, DEFAULT_SEARCH_OPTIONS, DEFAULT_FILE_OPTIONS, StorageFileApi, version = "2.117.2", DEFAULT_HEADERS, StorageBucketApi, StorageAnalyticsClient, VectorIndexApi, VectorDataApi, VectorBucketApi, StorageVectorsClient, VectorBucketScope, VectorIndexScope, StorageClient;
10997
+ }, DEFAULT_SEARCH_OPTIONS, DEFAULT_FILE_OPTIONS, StorageFileApi, version = "2.117.3", DEFAULT_HEADERS, StorageBucketApi, StorageAnalyticsClient, VectorIndexApi, VectorDataApi, VectorBucketApi, StorageVectorsClient, VectorBucketScope, VectorIndexScope, StorageClient;
10997
10998
  var init_dist3 = __esm(() => {
10998
10999
  init_dist2();
10999
11000
  StorageError = class extends Error {
@@ -11063,19 +11064,9 @@ var init_dist3 = __esm(() => {
11063
11064
  const options = _objectSpread22(_objectSpread22({}, DEFAULT_FILE_OPTIONS), fileOptions);
11064
11065
  let headers = _objectSpread22(_objectSpread22({}, _this.headers), method === "POST" && { "x-upsert": String(options.upsert) });
11065
11066
  const metadata = options.metadata;
11066
- if (typeof Blob !== "undefined" && fileBody instanceof Blob) {
11067
- body = new FormData;
11068
- body.append("cacheControl", options.cacheControl);
11069
- if (metadata)
11070
- body.append("metadata", _this.encodeMetadata(metadata));
11071
- body.append("", fileBody);
11072
- } else if (typeof FormData !== "undefined" && fileBody instanceof FormData) {
11073
- body = fileBody;
11074
- if (!body.has("cacheControl"))
11075
- body.append("cacheControl", options.cacheControl);
11076
- if (metadata && !body.has("metadata"))
11077
- body.append("metadata", _this.encodeMetadata(metadata));
11078
- } else {
11067
+ if (typeof Blob !== "undefined" && fileBody instanceof Blob || typeof FormData !== "undefined" && fileBody instanceof FormData)
11068
+ body = _this.prepareUploadFormData(fileBody, options);
11069
+ else {
11079
11070
  body = fileBody;
11080
11071
  headers["cache-control"] = `max-age=${options.cacheControl}`;
11081
11072
  headers["content-type"] = options.contentType;
@@ -11111,19 +11102,9 @@ var init_dist3 = __esm(() => {
11111
11102
  const options = _objectSpread22(_objectSpread22({}, DEFAULT_FILE_OPTIONS), fileOptions);
11112
11103
  let headers = _objectSpread22(_objectSpread22({}, _this3.headers), { "x-upsert": String(options.upsert) });
11113
11104
  const metadata = options.metadata;
11114
- if (typeof Blob !== "undefined" && fileBody instanceof Blob) {
11115
- body = new FormData;
11116
- body.append("cacheControl", options.cacheControl);
11117
- if (metadata)
11118
- body.append("metadata", _this3.encodeMetadata(metadata));
11119
- body.append("", fileBody);
11120
- } else if (typeof FormData !== "undefined" && fileBody instanceof FormData) {
11121
- body = fileBody;
11122
- if (!body.has("cacheControl"))
11123
- body.append("cacheControl", options.cacheControl);
11124
- if (metadata && !body.has("metadata"))
11125
- body.append("metadata", _this3.encodeMetadata(metadata));
11126
- } else {
11105
+ if (typeof Blob !== "undefined" && fileBody instanceof Blob || typeof FormData !== "undefined" && fileBody instanceof FormData)
11106
+ body = _this3.prepareUploadFormData(fileBody, options);
11107
+ else {
11127
11108
  body = fileBody;
11128
11109
  headers["cache-control"] = `max-age=${options.cacheControl}`;
11129
11110
  headers["content-type"] = options.contentType;
@@ -11320,6 +11301,26 @@ var init_dist3 = __esm(() => {
11320
11301
  return await post(_this15.fetch, `${_this15.url}/object/list-v2/${_this15.bucketId}`, body, { headers: _this15.headers }, parameters);
11321
11302
  });
11322
11303
  }
11304
+ prepareUploadFormData(fileBody, options) {
11305
+ const body = new FormData;
11306
+ const files = [];
11307
+ if (fileBody instanceof FormData)
11308
+ fileBody.forEach((value, name) => {
11309
+ if (typeof value === "string")
11310
+ body.append(name, value);
11311
+ else
11312
+ files.push([name, value]);
11313
+ });
11314
+ else
11315
+ files.push(["", fileBody]);
11316
+ if (!body.has("cacheControl"))
11317
+ body.append("cacheControl", options.cacheControl);
11318
+ if (options.metadata && !body.has("metadata"))
11319
+ body.append("metadata", this.encodeMetadata(options.metadata));
11320
+ for (const [name, file] of files)
11321
+ body.append(name, file);
11322
+ return body;
11323
+ }
11323
11324
  encodeMetadata(metadata) {
11324
11325
  return JSON.stringify(metadata);
11325
11326
  }
@@ -11760,14 +11761,14 @@ var init_dist3 = __esm(() => {
11760
11761
  };
11761
11762
  });
11762
11763
 
11763
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/version.js
11764
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/version.js
11764
11765
  var require_version2 = __commonJS(function(exports) {
11765
11766
  Object.defineProperty(exports, "__esModule", { value: true });
11766
11767
  exports.version = undefined;
11767
- exports.version = "2.117.2";
11768
+ exports.version = "2.117.3";
11768
11769
  });
11769
11770
 
11770
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/constants.js
11771
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/constants.js
11771
11772
  var require_constants2 = __commonJS(function(exports) {
11772
11773
  Object.defineProperty(exports, "__esModule", { value: true });
11773
11774
  exports.JWKS_TTL = exports.PKCE_MAX_CONCURRENT_FLOWS = exports.PKCE_FLOW_ID_PARAM = exports.BASE64URL_REGEX = exports.API_VERSIONS = exports.API_VERSION_HEADER_NAME = exports.NETWORK_FAILURE = exports.DEFAULT_HEADERS = exports.AUDIENCE = exports.STORAGE_KEY = exports.GOTRUE_URL = exports.REFRESH_FAILURE_COOLDOWN_MS = exports.EXPIRY_MARGIN_MS = exports.AUTO_REFRESH_TICK_THRESHOLD = exports.AUTO_REFRESH_TICK_DURATION_MS = undefined;
@@ -11797,7 +11798,7 @@ var require_constants2 = __commonJS(function(exports) {
11797
11798
  exports.JWKS_TTL = 10 * 60 * 1000;
11798
11799
  });
11799
11800
 
11800
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/errors.js
11801
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/errors.js
11801
11802
  var require_errors = __commonJS(function(exports) {
11802
11803
  Object.defineProperty(exports, "__esModule", { value: true });
11803
11804
  exports.AuthInvalidJwtError = exports.AuthWeakPasswordError = exports.AuthRefreshDiscardedError = exports.AuthRetryableFetchError = exports.AuthPKCECodeVerifierMissingError = exports.AuthPKCEGrantCodeExchangeError = exports.AuthImplicitGrantRedirectError = exports.AuthInvalidCredentialsError = exports.AuthInvalidTokenResponseError = exports.AuthSessionMissingError = exports.CustomAuthError = exports.AuthUnknownError = exports.AuthApiError = exports.AuthError = undefined;
@@ -11966,7 +11967,7 @@ var require_errors = __commonJS(function(exports) {
11966
11967
  exports.AuthInvalidJwtError = AuthInvalidJwtError;
11967
11968
  });
11968
11969
 
11969
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/base64url.js
11970
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/base64url.js
11970
11971
  var require_base64url = __commonJS(function(exports) {
11971
11972
  Object.defineProperty(exports, "__esModule", { value: true });
11972
11973
  exports.byteToBase64URL = byteToBase64URL;
@@ -12154,7 +12155,7 @@ var require_base64url = __commonJS(function(exports) {
12154
12155
  }
12155
12156
  });
12156
12157
 
12157
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/helpers.js
12158
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/helpers.js
12158
12159
  var require_helpers = __commonJS(function(exports) {
12159
12160
  Object.defineProperty(exports, "__esModule", { value: true });
12160
12161
  exports.pkceVerifierSlotKey = exports.Deferred = exports.removeItemAsync = exports.getItemAsync = exports.setItemAsync = exports.looksLikeFetchResponse = exports.resolveFetch = exports.supportsLocalStorage = exports.isBrowser = undefined;
@@ -12563,7 +12564,7 @@ var require_helpers = __commonJS(function(exports) {
12563
12564
  }
12564
12565
  });
12565
12566
 
12566
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/fetch.js
12567
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/fetch.js
12567
12568
  var require_fetch = __commonJS(function(exports) {
12568
12569
  Object.defineProperty(exports, "__esModule", { value: true });
12569
12570
  exports.handleError = handleError;
@@ -12747,14 +12748,14 @@ var require_fetch = __commonJS(function(exports) {
12747
12748
  }
12748
12749
  });
12749
12750
 
12750
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/types.js
12751
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/types.js
12751
12752
  var require_types2 = __commonJS(function(exports) {
12752
12753
  Object.defineProperty(exports, "__esModule", { value: true });
12753
12754
  exports.SIGN_OUT_SCOPES = undefined;
12754
12755
  exports.SIGN_OUT_SCOPES = ["global", "local", "others"];
12755
12756
  });
12756
12757
 
12757
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/GoTrueAdminApi.js
12758
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/GoTrueAdminApi.js
12758
12759
  var require_GoTrueAdminApi = __commonJS(function(exports) {
12759
12760
  Object.defineProperty(exports, "__esModule", { value: true });
12760
12761
  var tslib_1 = require_tslib();
@@ -13199,7 +13200,7 @@ var require_GoTrueAdminApi = __commonJS(function(exports) {
13199
13200
  exports.default = GoTrueAdminApi;
13200
13201
  });
13201
13202
 
13202
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/local-storage.js
13203
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/local-storage.js
13203
13204
  var require_local_storage = __commonJS(function(exports) {
13204
13205
  Object.defineProperty(exports, "__esModule", { value: true });
13205
13206
  exports.memoryLocalStorageAdapter = memoryLocalStorageAdapter;
@@ -13218,7 +13219,7 @@ var require_local_storage = __commonJS(function(exports) {
13218
13219
  }
13219
13220
  });
13220
13221
 
13221
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/locks.js
13222
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/locks.js
13222
13223
  var require_locks = __commonJS(function(exports) {
13223
13224
  Object.defineProperty(exports, "__esModule", { value: true });
13224
13225
  exports.ProcessLockAcquireTimeoutError = exports.NavigatorLockAcquireTimeoutError = exports.LockAcquireTimeoutError = exports.internals = undefined;
@@ -13390,7 +13391,7 @@ var require_locks = __commonJS(function(exports) {
13390
13391
  }
13391
13392
  });
13392
13393
 
13393
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/polyfills.js
13394
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/polyfills.js
13394
13395
  var require_polyfills = __commonJS(function(exports) {
13395
13396
  Object.defineProperty(exports, "__esModule", { value: true });
13396
13397
  exports.polyfillGlobalThis = polyfillGlobalThis;
@@ -13414,7 +13415,7 @@ var require_polyfills = __commonJS(function(exports) {
13414
13415
  }
13415
13416
  });
13416
13417
 
13417
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/web3/ethereum.js
13418
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/web3/ethereum.js
13418
13419
  var require_ethereum = __commonJS(function(exports) {
13419
13420
  Object.defineProperty(exports, "__esModule", { value: true });
13420
13421
  exports.getAddress = getAddress;
@@ -13491,7 +13492,7 @@ ${suffix}`;
13491
13492
  }
13492
13493
  });
13493
13494
 
13494
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/webauthn.errors.js
13495
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/webauthn.errors.js
13495
13496
  var require_webauthn_errors = __commonJS(function(exports) {
13496
13497
  Object.defineProperty(exports, "__esModule", { value: true });
13497
13498
  exports.WebAuthnUnknownError = exports.WebAuthnError = undefined;
@@ -13678,7 +13679,7 @@ var require_webauthn_errors = __commonJS(function(exports) {
13678
13679
  }
13679
13680
  });
13680
13681
 
13681
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/lib/webauthn.js
13682
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/lib/webauthn.js
13682
13683
  var require_webauthn = __commonJS(function(exports) {
13683
13684
  Object.defineProperty(exports, "__esModule", { value: true });
13684
13685
  exports.WebAuthnApi = exports.DEFAULT_REQUEST_OPTIONS = exports.DEFAULT_CREATION_OPTIONS = exports.webAuthnAbortService = exports.WebAuthnAbortService = exports.identifyAuthenticationError = exports.identifyRegistrationError = exports.isWebAuthnError = exports.WebAuthnError = undefined;
@@ -14132,7 +14133,7 @@ var require_webauthn = __commonJS(function(exports) {
14132
14133
  exports.WebAuthnApi = WebAuthnApi;
14133
14134
  });
14134
14135
 
14135
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/GoTrueClient.js
14136
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/GoTrueClient.js
14136
14137
  var require_GoTrueClient = __commonJS(function(exports) {
14137
14138
  Object.defineProperty(exports, "__esModule", { value: true });
14138
14139
  var tslib_1 = require_tslib();
@@ -17015,7 +17016,7 @@ var require_GoTrueClient = __commonJS(function(exports) {
17015
17016
  exports.default = GoTrueClient;
17016
17017
  });
17017
17018
 
17018
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/AuthAdminApi.js
17019
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/AuthAdminApi.js
17019
17020
  var require_AuthAdminApi = __commonJS(function(exports) {
17020
17021
  Object.defineProperty(exports, "__esModule", { value: true });
17021
17022
  var tslib_1 = require_tslib();
@@ -17024,7 +17025,7 @@ var require_AuthAdminApi = __commonJS(function(exports) {
17024
17025
  exports.default = AuthAdminApi;
17025
17026
  });
17026
17027
 
17027
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/AuthClient.js
17028
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/AuthClient.js
17028
17029
  var require_AuthClient = __commonJS(function(exports) {
17029
17030
  Object.defineProperty(exports, "__esModule", { value: true });
17030
17031
  var tslib_1 = require_tslib();
@@ -17033,7 +17034,7 @@ var require_AuthClient = __commonJS(function(exports) {
17033
17034
  exports.default = AuthClient;
17034
17035
  });
17035
17036
 
17036
- // ../../node_modules/.bun/@supabase+auth-js@2.117.2/node_modules/@supabase/auth-js/dist/main/index.js
17037
+ // ../../node_modules/.bun/@supabase+auth-js@2.117.3/node_modules/@supabase/auth-js/dist/main/index.js
17037
17038
  var require_main3 = __commonJS(function(exports) {
17038
17039
  Object.defineProperty(exports, "__esModule", { value: true });
17039
17040
  exports.processLock = exports.lockInternals = exports.NavigatorLockAcquireTimeoutError = exports.navigatorLock = exports.AuthClient = exports.AuthAdminApi = exports.GoTrueClient = exports.GoTrueAdminApi = undefined;
@@ -17063,7 +17064,7 @@ var require_main3 = __commonJS(function(exports) {
17063
17064
  } });
17064
17065
  });
17065
17066
 
17066
- // ../../node_modules/.bun/@supabase+supabase-js@2.117.2/node_modules/@supabase/supabase-js/dist/index.mjs
17067
+ // ../../node_modules/.bun/@supabase+supabase-js@2.117.3/node_modules/@supabase/supabase-js/dist/index.mjs
17067
17068
  var exports_dist = {};
17068
17069
  __export(exports_dist, {
17069
17070
  createClient: () => createClient
@@ -17286,7 +17287,7 @@ function shouldShowDeprecationWarning() {
17286
17287
  return false;
17287
17288
  return parseInt(versionMatch[1], 10) <= 20;
17288
17289
  }
17289
- var import_functions_js, import_realtime_js, import_auth_js, version2 = "2.117.2", JS_ENV = "", JS_RUNTIME_VERSION, _Deno$version, _process$version, _runtimeMeta, DEFAULT_HEADERS2, DEFAULT_GLOBAL_OPTIONS, DEFAULT_DB_OPTIONS, DEFAULT_AUTH_OPTIONS, DEFAULT_REALTIME_OPTIONS, DEFAULT_TRACE_PROPAGATION_OPTIONS, resolveFetch2 = (customFetch) => {
17290
+ var import_functions_js, import_realtime_js, import_auth_js, version2 = "2.117.3", JS_ENV = "", JS_RUNTIME_VERSION, _Deno$version, _process$version, _runtimeMeta, DEFAULT_HEADERS2, DEFAULT_GLOBAL_OPTIONS, DEFAULT_DB_OPTIONS, DEFAULT_AUTH_OPTIONS, DEFAULT_REALTIME_OPTIONS, DEFAULT_TRACE_PROPAGATION_OPTIONS, resolveFetch2 = (customFetch) => {
17290
17291
  if (customFetch)
17291
17292
  return (...args) => customFetch(...args);
17292
17293
  return (...args) => fetch(...args);
@@ -23684,6 +23685,23 @@ function auditDocLabel(docTitle, documentId, operation) {
23684
23685
  return documentId.slice(0, 8) + "…";
23685
23686
  return isStoreLevelAuditOp(operation) ? "(store)" : "(deleted)";
23686
23687
  }
23688
+ function metadataChangeSummary(before, after) {
23689
+ if (before == null && after == null)
23690
+ return null;
23691
+ const b = before ?? {};
23692
+ const a = after ?? {};
23693
+ const show = (v) => typeof v === "string" ? v : JSON.stringify(v);
23694
+ const set = Object.keys(a).filter((k) => JSON.stringify(a[k]) !== JSON.stringify(b[k])).map((k) => `${k}=${show(a[k])}`);
23695
+ const removed = Object.keys(b).filter((k) => !Object.hasOwn(a, k));
23696
+ if (set.length === 0 && removed.length === 0)
23697
+ return null;
23698
+ const parts = [];
23699
+ if (set.length)
23700
+ parts.push(`set ${set.join(", ")}`);
23701
+ if (removed.length)
23702
+ parts.push(`removed ${removed.join(", ")}`);
23703
+ return parts.join("; ");
23704
+ }
23687
23705
  var STORE_LEVEL_AUDIT_OPS, AUDIT_OPERATIONS;
23688
23706
  var init_audit_ops = __esm(() => {
23689
23707
  STORE_LEVEL_AUDIT_OPS = [
@@ -23728,6 +23746,25 @@ function getConfiguredSearchAlpha() {
23728
23746
  function getMinTermCoverage() {
23729
23747
  return;
23730
23748
  }
23749
+ function metadataTokenParams(expected, lastWriteWins) {
23750
+ return {
23751
+ ...expected != null ? { p_expected_metadata_revision: expected } : {},
23752
+ ...lastWriteWins ? { p_last_write_wins: true } : {}
23753
+ };
23754
+ }
23755
+ function metadataTokenError(message) {
23756
+ if (message.includes("CEREFOX_METADATA_CONFLICT")) {
23757
+ const id = message.match(/document ([0-9a-f-]{36})/)?.[1] ?? "(unknown)";
23758
+ const expected = message.match(/expected metadata_revision (\d+)/)?.[1] ?? "?";
23759
+ const current = message.match(/current (\d+)/)?.[1] ?? "?";
23760
+ return new Error(`Metadata conflict: the metadata of document ${id} changed since you read it ` + `(your metadata_revision: ${expected}, current: ${current}). Nothing was written. ` + `To resolve: (1) cerefox_get_document("${id}", outline: true) for the current metadata ` + `and metadata_revision, (2) reapply your change to it, (3) retry with ` + `expected_metadata_revision set to the new value. Do not overwrite blindly — ` + `another writer's keys may be in there.`);
23761
+ }
23762
+ if (message.includes("CEREFOX_METADATA_TOKEN_REQUIRED")) {
23763
+ const current = message.match(/Current metadata_revision: (\d+)/)?.[1];
23764
+ return new Error(`Replacing all metadata deletes every key you do not list, so it needs ` + `expected_metadata_revision: the metadata_revision you read (returned by ` + `cerefox_get_document and cerefox_metadata_search).` + (current ? ` The document's current metadata_revision is ${current}; pass it ONLY if you read the current metadata.` : "") + ` To change some keys, drop replace and merge instead (null removes a key). ` + `To overwrite regardless, pass last_write_wins=true.`);
23765
+ }
23766
+ return null;
23767
+ }
23731
23768
  function extractConflictHashes(message) {
23732
23769
  return {
23733
23770
  expected: message.match(/expected hash ([0-9a-f]{64})/)?.[1] ?? "unknown",
@@ -23740,6 +23777,16 @@ function isDocumentNotFoundError(error) {
23740
23777
  function isAuditCheckError(message) {
23741
23778
  return /cerefox_audit_log_operation_check/.test(message);
23742
23779
  }
23780
+ function duplicateContentDocumentId(message) {
23781
+ if (!message.includes("CEREFOX_DUPLICATE_CONTENT"))
23782
+ return null;
23783
+ return message.match(/document ([0-9a-f-]{36})/i)?.[1] ?? null;
23784
+ }
23785
+ function legacyContentUniqueError(message) {
23786
+ if (!message.includes("cerefox_documents_hash_unique"))
23787
+ return null;
23788
+ return new Error("Another document (possibly one in the trash) already has exactly this content, " + "and this server predates schema 0.20.0, which still refuses that. " + `Run \`cerefox server deploy\`, then retry. (${message})`);
23789
+ }
23743
23790
  function isDuplicateKeyError(message) {
23744
23791
  return /duplicate key|unique constraint|23505/i.test(message);
23745
23792
  }
@@ -23775,9 +23822,25 @@ function resolveByteBudget(requested, ceiling) {
23775
23822
  return ceiling;
23776
23823
  return Math.min(Math.max(n, 1), ceiling);
23777
23824
  }
23778
- var MAX_RESPONSE_BYTES = 200000;
23825
+ var MAX_RESPONSE_BYTES = 200000, MetadataConcurrencyError, DuplicateContentError;
23779
23826
  var init__utils = __esm(() => {
23780
23827
  init_audit_ops();
23828
+ MetadataConcurrencyError = class MetadataConcurrencyError extends Error {
23829
+ tokenRequired;
23830
+ constructor(message) {
23831
+ super(message);
23832
+ this.name = "MetadataConcurrencyError";
23833
+ this.tokenRequired = message.includes("CEREFOX_METADATA_TOKEN_REQUIRED");
23834
+ }
23835
+ };
23836
+ DuplicateContentError = class DuplicateContentError extends Error {
23837
+ existingDocumentId;
23838
+ constructor(existingDocumentId, message) {
23839
+ super(message);
23840
+ this.existingDocumentId = existingDocumentId;
23841
+ this.name = "DuplicateContentError";
23842
+ }
23843
+ };
23781
23844
  });
23782
23845
 
23783
23846
  // ../../_shared/server-assets/index.ts
@@ -25967,13 +26030,17 @@ async function updateDocumentFacets(supabase, opts) {
25967
26030
  };
25968
26031
  if (opts.metadata !== undefined) {
25969
26032
  await step(async () => {
25970
- const { data: doc, error } = await supabase.from("cerefox_documents").select("metadata").eq("id", opts.documentId).is("deleted_at", null).limit(1);
26033
+ const { data: doc, error } = await supabase.from("cerefox_documents").select("*").eq("id", opts.documentId).is("deleted_at", null).limit(1);
25971
26034
  if (error)
25972
26035
  throw new Error(`Metadata read failed: ${error.message}`);
25973
26036
  if (!doc?.length) {
25974
26037
  throw new FacetNotFoundError(`Document not found (or in the trash): ${opts.documentId}`);
25975
26038
  }
25976
26039
  const stored = doc[0].metadata ?? {};
26040
+ const storedRevision = doc[0].metadata_revision;
26041
+ if (opts.expectedMetadataRevision != null && storedRevision != null && storedRevision !== opts.expectedMetadataRevision && !opts.metadataLastWriteWins) {
26042
+ throw new MetadataConcurrencyError(`CEREFOX_METADATA_CONFLICT: the metadata of document ${opts.documentId} changed since it was read ` + `(expected metadata_revision ${opts.expectedMetadataRevision}, current ${storedRevision}).`);
26043
+ }
25977
26044
  const wanted = normalizeMetadata(opts.metadata);
25978
26045
  if (stableStringify(stored) !== stableStringify(wanted)) {
25979
26046
  const { error: rpcErr } = await supabase.rpc("cerefox_set_document_metadata", {
@@ -25981,10 +26048,13 @@ async function updateDocumentFacets(supabase, opts) {
25981
26048
  p_metadata: wanted,
25982
26049
  p_replace: true,
25983
26050
  p_author: opts.author,
25984
- p_author_type: opts.authorType
26051
+ p_author_type: opts.authorType,
26052
+ ...metadataTokenParams(opts.expectedMetadataRevision, opts.metadataLastWriteWins)
25985
26053
  });
25986
26054
  if (rpcErr) {
25987
26055
  const msg = rpcErr.message ?? String(rpcErr);
26056
+ if (msg.includes("CEREFOX_METADATA_"))
26057
+ throw new MetadataConcurrencyError(msg);
25988
26058
  if (/not found/i.test(msg))
25989
26059
  throw new FacetNotFoundError(msg);
25990
26060
  throw new Error(`Metadata update failed: ${msg}`);
@@ -26362,7 +26432,7 @@ var init_compatibility = __esm(() => {
26362
26432
  };
26363
26433
  });
26364
26434
 
26365
- // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/conninfo.mjs
26435
+ // ../../node_modules/.bun/@hono+node-server@2.1.4+c539cad7f494f5c9/node_modules/@hono/node-server/dist/conninfo.mjs
26366
26436
  var getConnInfo = (c) => {
26367
26437
  const bindings = c.env.server ? c.env.server : c.env;
26368
26438
  const address = bindings.incoming.socket.remoteAddress;
@@ -26778,7 +26848,7 @@ var init_update_check = __esm(() => {
26778
26848
  });
26779
26849
 
26780
26850
  // ../../_shared/ef-meta/index.ts
26781
- var EF_VERSION = "1.18.0", CEREFOX_VERSION = "1.18.0", EF_LAST_CHANGED = "1.17.3";
26851
+ var EF_VERSION = "1.19.1", CEREFOX_VERSION = "1.19.1", EF_LAST_CHANGED = "1.19.0";
26782
26852
  var init_ef_meta = () => {};
26783
26853
 
26784
26854
  // ../../_shared/embeddings/onnx-embedder.ts
@@ -26933,9 +27003,7 @@ async function ensurePipeline() {
26933
27003
  pipelinePromise = (async () => {
26934
27004
  const transformers = await loadTransformers();
26935
27005
  if (isModelCached()) {
26936
- const pipe = await transformers.pipeline("feature-extraction", ONNX_MODEL_ID, {
26937
- dtype: ONNX_MODEL_DTYPE
26938
- });
27006
+ const pipe = await transformers.pipeline("feature-extraction", ONNX_MODEL_ID, PIPELINE_OPTIONS);
26939
27007
  return pipe;
26940
27008
  }
26941
27009
  const mb = ONNX_MODEL_APPROX_MB;
@@ -27016,9 +27084,7 @@ async function ensurePipeline() {
27016
27084
  }
27017
27085
  };
27018
27086
  await downloadModelFiles(String(transformers.env.remoteHost ?? "https://huggingface.co/"), (file, loaded, total) => progressCallback({ status: "progress", file, loaded, total }), (file, size) => progressCallback({ status: "done", file, total: size }));
27019
- const pipe = await transformers.pipeline("feature-extraction", ONNX_MODEL_ID, {
27020
- dtype: ONNX_MODEL_DTYPE
27021
- });
27087
+ const pipe = await transformers.pipeline("feature-extraction", ONNX_MODEL_ID, PIPELINE_OPTIONS);
27022
27088
  process.stderr.write(`[cerefox-embed] embedder ready.
27023
27089
  `);
27024
27090
  return pipe;
@@ -27061,7 +27127,7 @@ async function onnxEmbed(texts, role) {
27061
27127
  }
27062
27128
  return vectors;
27063
27129
  }
27064
- var ONNX_MODEL_ID = "nomic-ai/nomic-embed-text-v1.5", ONNX_MODEL_NAME = "nomic-embed-text-v1.5", ONNX_MODEL_DTYPE = "q8", ONNX_MODEL_DIM = 768, ONNX_MODEL_APPROX_MB = 130, MODEL_REPO_FILES, MODEL_FILES, EmbedderDownloadError, DOWNLOAD_ATTEMPTS = 6, SEGMENT_BYTES, REQUEST_TIMEOUT_MS = 120000, transformersModule = null, pipelinePromise = null, DEFAULT_ONNX_BATCH = 4;
27130
+ var ONNX_MODEL_ID = "nomic-ai/nomic-embed-text-v1.5", ONNX_MODEL_NAME = "nomic-embed-text-v1.5", ONNX_MODEL_DTYPE = "q8", ONNX_MODEL_DIM = 768, ONNX_MODEL_APPROX_MB = 130, MODEL_REPO_FILES, MODEL_FILES, EmbedderDownloadError, DOWNLOAD_ATTEMPTS = 6, SEGMENT_BYTES, REQUEST_TIMEOUT_MS = 120000, transformersModule = null, pipelinePromise = null, PIPELINE_OPTIONS, DEFAULT_ONNX_BATCH = 1;
27065
27131
  var init_onnx_embedder = __esm(() => {
27066
27132
  MODEL_REPO_FILES = [
27067
27133
  "config.json",
@@ -27078,6 +27144,10 @@ var init_onnx_embedder = __esm(() => {
27078
27144
  }
27079
27145
  };
27080
27146
  SEGMENT_BYTES = 4 * 1024 * 1024;
27147
+ PIPELINE_OPTIONS = {
27148
+ dtype: ONNX_MODEL_DTYPE,
27149
+ session_options: { enableCpuMemArena: false }
27150
+ };
27081
27151
  });
27082
27152
 
27083
27153
  // ../../_shared/embeddings/index.ts
@@ -50983,7 +51053,8 @@ async function handler(supabase, args, ctx) {
50983
51053
  const lines = entries.map((e) => {
50984
51054
  const docLabel = auditDocLabel(e.doc_title, e.document_id, e.operation);
50985
51055
  const sizeInfo = e.size_before != null && e.size_after != null ? ` | ${e.size_before} -> ${e.size_after} chars` : e.size_after != null ? ` | ${e.size_after} chars` : "";
50986
- return `${utcStamp2(e.created_at)} | ${e.operation} | ${e.author} (${e.author_type}) | ${docLabel}${sizeInfo} | ${e.description}`;
51056
+ const change = metadataChangeSummary(e.metadata_before, e.metadata_after);
51057
+ return `${utcStamp2(e.created_at)} | ${e.operation} | ${e.author} (${e.author_type}) | ${docLabel}${sizeInfo} | ${e.description}${change ? ` | ${change}` : ""}`;
50987
51058
  });
50988
51059
  return `Audit log (${entries.length} entries, newest first):
50989
51060
 
@@ -51510,6 +51581,8 @@ async function handler4(supabase, args, ctx) {
51510
51581
  return JSON.stringify({
51511
51582
  title: row.doc_title ?? "Untitled",
51512
51583
  content_hash: archived ? null : row.content_hash ?? null,
51584
+ metadata: row.doc_metadata ?? {},
51585
+ metadata_revision: archived ? null : row.metadata_revision ?? null,
51513
51586
  total_chars: row.total_chars ?? (row.full_content ?? "").length,
51514
51587
  outline: nodes,
51515
51588
  note: archived ? "This is an ARCHIVED version's structure, so no content_hash is returned: these anchors describe the old version and must not be used to edit the current one. Re-read without version_id to edit." : nodes.length === 0 ? "This document has no headings, so it has no anchors: only end_of_document inserts apply." : "Use a path as anchor_heading in cerefox_insert / cerefox_edit; content_hash is your expected_content_hash."
@@ -51536,7 +51609,8 @@ async function handler4(supabase, args, ctx) {
51536
51609
  }, null, 2);
51537
51610
  }
51538
51611
  const label = version_id !== null ? " (archived version)" : " (current)";
51539
- const hashLine = row.content_hash ? `content_hash: ${row.content_hash}
51612
+ const revision = version_id === null && row.metadata_revision != null ? ` · metadata_revision: ${row.metadata_revision}` : "";
51613
+ const hashLine = row.content_hash ? `content_hash: ${row.content_hash}${revision}
51540
51614
 
51541
51615
  ` : "";
51542
51616
  return `# ${row.doc_title ?? "Untitled"}${label}
@@ -51716,9 +51790,6 @@ async function applyAndWrite(supabase, ctx, args) {
51716
51790
  if (message.includes("CEREFOX_CONFLICT")) {
51717
51791
  throw conflictError2(documentId, expectedHash, extractConflictHashes(message).current);
51718
51792
  }
51719
- if (message.includes("cerefox_documents_hash_unique")) {
51720
- throw new Error(`This edit would make "${doc.title}" (id: ${documentId}) byte-identical to another ` + `document in the store, and content must be unique. No write was performed. ` + `Usually this means the two documents have converged and one should be removed or ` + `merged, or that this edit was already applied to the other one — ` + `cerefox_search for the resulting content to find it.`);
51721
- }
51722
51793
  if (message.includes("CEREFOX_UNRESOLVED_LINKS")) {
51723
51794
  const ids = message.match(/do not exist: ([^.]+)\./)?.[1] ?? "(unparsed)";
51724
51795
  throw new Error(`Edit rejected — this edit introduces link(s) to document id(s) that do not ` + `exist: ${ids}. The UUIDs are almost certainly mangled — re-read the source ` + `you copied each link from, correct the id(s), and retry. Do not retry ` + `unchanged. Deliberate examples belong in code formatting (backticks).`);
@@ -51729,6 +51800,9 @@ async function applyAndWrite(supabase, ctx, args) {
51729
51800
  if (isMissingFunctionError(message, "cerefox_ingest_document")) {
51730
51801
  throw new Error(`This server is behind: partial edits need schema 0.11.0 or newer. ` + `Run \`cerefox server deploy\`, then retry. (${message})`);
51731
51802
  }
51803
+ const legacy = legacyContentUniqueError(message);
51804
+ if (legacy)
51805
+ throw legacy;
51732
51806
  throw new Error(`Edit failed: ${message}`);
51733
51807
  }
51734
51808
  const row = data?.[0] ?? undefined;
@@ -51935,12 +52009,12 @@ var init_partial_edits2 = __esm(() => {
51935
52009
  });
51936
52010
 
51937
52011
  // ../../_shared/mcp-tools/get-help-content.ts
51938
- var HELP_FULL = '# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **15 core MCP tools** (14 with CLI equivalents — `cerefox_get_help` is MCP-only), plus 4 dormant relation tools that appear only when `relations_enabled` is on. For the full guide, search Cerefox for "How AI Agents Use Cerefox" or call `cerefox_get_help` to retrieve this content over MCP.\n\n## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `author` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 10), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part`, `author` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: "## Heading"` one section\'s text | `document_id` (required), `outline`, `section`, `section_part`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type`, `author` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project\'s docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object), `author` |\n| `cerefox_set_document_projects` | Set doc\'s project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.\n\n## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so "I meant to append" cannot become "I replaced the file".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: "## Heading")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section\'s *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.\n\n## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`** to your name on every call, reads and writes alike (e.g., "Claude Code", "archiver"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never "No results found." — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n7. **Write for retrieval: the title does real work.** The document title is indexed at the **highest weight** *and* prepended to every chunk before it is embedded, so it steers both halves of hybrid search (keyword and semantic) for every chunk in the document. Name the document with the distinctive terms someone would actually search for -- "Cerefox Response Size Limits", not "Notes" or "Update 3". Renaming re-indexes and re-embeds, so correcting a vague title is cheap and takes effect immediately. Write the characters themselves: `&` and `<>`, never `&amp;` or `&lt;&gt;` (entities are stored literally and the write reply will say so). Inside the content, write structured Markdown with H1/H2/H3 headings: the chunker follows them, and a chunk\'s own heading carries the same weight as the title.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document\'s `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good; so is one auto-purged after `trash_retention_days`, if the operator has turned trash auto-purge on.\n9. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n11. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n12. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behavior. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n13. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch("topic") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title="Same Title", content="...", document_id="abc123",\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch("topic") -> find doc (note its hash) -> modify ->\ningest(title="Same Title", content="...", update_if_exists=true,\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={"type": "decision-log"}, updated_since="2026-03-28T00:00:00Z")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search "<q>" --author "<your-name>"` |\n| `cerefox_ingest` (paste) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --author "<your-name>" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --document-id "<uuid>" --expected-content-hash "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --author "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --author "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --author "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_insert` | `cerefox document insert <id> -t "<text>" -p <position> -a "<anchor-heading>" -e "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_edit` | `cerefox document edit-parts <id> --operations \'<json>\' -e "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_delete_document` | `cerefox document delete <id> --reason "<why>" --author "<your-name>" --author-type agent --yes` (confirms interactively instead of requiring the hash) |\n| `cerefox_restore_document` | `cerefox document restore <id> --reason "<why>" --author "<your-name>" --author-type agent` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --author "<your-name>"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_metadata` | `cerefox document set-metadata <id> --set key=value` (also `--remove key`, `--json \'{...}\'`, `--replace`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author "<your-name>" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --author "<your-name>"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author "<your-name>" --author-type agent`\n- Reads: `--author "<your-name>"` (the same flag; `--requestor` still works as a hidden alias)\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n\n## Timestamps are UTC\n\nEvery timestamp Cerefox returns — `created_at` on audit entries, version\nhistory, document metadata — is **UTC**, and now carries its `Z` marker so it\ncannot be mistaken for local time.\n\n**When you write a date into a document\'s CONTENT, use your own clock, not a\nCerefox timestamp.** These are different things: a timestamp records when the\nserver stored something; a date in a log entry or a heading is authored content\nand belongs to your timezone. An agent working a Pacific afternoon read\n`2026-08-11` from version history, wrote "8/11" into its entries, and put a\nday\'s work in the future — the timestamp was correct, and copying it into\ncontent was not.\n\nCerefox deliberately does not convert to local time on the API or MCP paths.\n"Local" has no server-side meaning: the remote MCP server runs in a cloud\nfunction whose local time *is* UTC, while a local MCP server runs in yours, so\nthe same document would report two different times depending on transport. The\nweb UI converts because a browser knows the viewer\'s timezone; nothing\nserver-side does.\n\n## Mistakes that have actually happened\n\nEach of these comes from a real agent session, and each is easy to make.\n\n- **`cerefox_ingest` always replaces the ENTIRE document.** Never a section.\n Before sending, check that the tool name matches the intent: if the intent is\n "change one section", the call is `cerefox_edit` with `replace_section`. A\n section-sized edit sent as a full ingest truncated a 13,000-character index to\n a single word. It was recovered from version history within the minute, but\n only because it was noticed immediately.\n\n- **Do not include the anchor\'s own heading in your text.** `replace_section`\n keeps the heading and `insert` places your text inside the section, so\n including it produces two. This is now refused rather than silently applied,\n but the shape is worth knowing: it happened twice in one session, the second\n time while trying to repair the first. A *deeper* sub-heading inside your text\n is fine.\n\n- **Content between sections belongs to the section ABOVE it.** A section runs\n to the next heading of the same or higher level, so a `---` rule, a note, or\n any trailing text sitting just above the next heading is part of the section\n before it — even when it visually reads as belonging below. Replacing that\n section takes it too. An agent hit exactly this: a `---` that separated two\n major sections disappeared when the section above it was replaced. The write\n was correct by the addressing rules; the surprise is that "the end of this\n section" is further down the page than it looks. Note the loss warning will\n not catch it if your replacement text is longer than what it replaced, since\n there is then no net loss to report.\n\n- **To change only tags, use `cerefox_set_document_metadata`, never `cerefox_ingest`.**\n Ingest replaces the whole document, so re-sending it to set one tag carries the\n full transcription risk for no reason. The metadata tool merges: the keys you\n pass are set, everything else is left alone, so you do not need to read the\n document first and cannot drop a tag another agent set. Pass `null` as a value\n to remove a key.\n\n- **Never partial-edit to fix a partial edit.** If a write leaves unexpected\n structure, stop. Use `cerefox_list_versions`, retrieve the last good version,\n and re-ingest cleanly. Repairing edits with more edits compounds the damage.\n\n- **A rejected batch is safe.** Operations in one `cerefox_edit` are\n all-or-nothing: if any is invalid, nothing is written. A refusal costs you a\n retry, not data — so prefer one call for changes that belong together, and do\n not split a batch to "make it more likely to succeed".\n\n- **Read before replacing.** `cerefox_get_document(section: "## Heading")`\n returns exactly what a `replace_section` on that anchor would overwrite. Use it\n for any section you did not write in this session. The outline gives a\n section\'s *size*, never its *text*.\n\n- **Verify after writing** — read the result back before reporting success, and\n report what the read actually shows.\n\n- **Partial edits cannot change a document\'s stored TITLE.** `rename_section`\n changes a heading inside the content; the title is a separate field and still\n needs `cerefox_ingest`.\n\n- **If a capability seems missing from one server, suspect your client first.**\n Local and remote run the same code. **Every `cerefox_get_help()` response\n begins with the server\'s version and the operations it registers** — you do\n not need a special topic, and the *absence* of that block is itself an answer:\n a server that does not print it predates v1.5.0. If that\n disagrees with your tool list, the client is holding a list it fetched before\n an upgrade — clients cache it at connect time. Ask the user to restart the\n client. Do not record a capability difference between servers as a fact; every\n such report so far has been a stale client.\n- Long inline bodies can arrive with literal `\\n`/`\\"` (the author over-escaped; Cerefox stores bytes faithfully). For long or quote-dense content, ingest from a file or build incrementally with `cerefox_insert`; read back multi-line writes.\n', HELP_SECTIONS, HELP_SECTION_HEADINGS;
52012
+ var HELP_FULL = '# Cerefox Knowledge Base -- Agent Quick Reference\n\nCerefox is a persistent, shared knowledge base. You have **15 core MCP tools** (14 with CLI equivalents — `cerefox_get_help` is MCP-only), plus 4 dormant relation tools that appear only when `relations_enabled` is on. For the full guide, search Cerefox for "How AI Agents Use Cerefox" or call `cerefox_get_help` to retrieve this content over MCP.\n\n## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `author` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 10), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (on update it **merges**: `null` removes a key, omitted keys are kept), `expected_metadata_revision` (optional, see rule 10), `author` |\n| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part`, `author` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: "## Heading"` one section\'s text | `document_id` (required), `outline`, `section`, `section_part`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type`, `author` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project\'s docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object; needs `expected_metadata_revision`), `expected_metadata_revision`, `author` |\n| `cerefox_set_document_projects` | Set doc\'s project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.\n\n## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so "I meant to append" cannot become "I replaced the file".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: "## Heading")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section\'s *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.\n\n## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`** to your name on every call, reads and writes alike (e.g., "Claude Code", "archiver"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never "No results found." — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n7. **Write for retrieval: the title does real work.** The document title is indexed at the **highest weight** *and* prepended to every chunk before it is embedded, so it steers both halves of hybrid search (keyword and semantic) for every chunk in the document. Name the document with the distinctive terms someone would actually search for -- "Cerefox Response Size Limits", not "Notes" or "Update 3". Renaming re-indexes and re-embeds, so correcting a vague title is cheap and takes effect immediately. Write the characters themselves: `&` and `<>`, never `&amp;` or `&lt;&gt;` (entities are stored literally and the write reply will say so). Inside the content, write structured Markdown with H1/H2/H3 headings: the chunker follows them, and a chunk\'s own heading carries the same weight as the title.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document\'s `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good; so is one auto-purged after `trash_retention_days`, if the operator has turned trash auto-purge on.\n9. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict. **Metadata has its own token, `metadata_revision`** (shown next to `content_hash` wherever it appears). It is optional on a merge (pass it when your change depends on the current values, e.g. a status transition) and **required with `replace: true`**. A stale one is a *metadata conflict*: re-read, reapply, retry.\n11. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n12. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behavior. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n13. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.\n\n## Update Workflow (ID-based -- preferred)\n\n```\nsearch("topic") -> find doc [id: abc123] -> get_document(abc123) -> note its content_hash -> modify ->\ningest(title="Same Title", content="...", document_id="abc123",\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\nOn a **conflict** error: get_document again (fresh content + fresh hash) -> merge your changes -> retry with the new hash.\n\n## Update Workflow (title-based -- fallback)\n\n```\nsearch("topic") -> find doc (note its hash) -> modify ->\ningest(title="Same Title", content="...", update_if_exists=true,\n expected_content_hash="<the hash you read>", author="my-agent")\n```\n\n## Catch-Up Workflow\n\n```\nmetadata_search(metadata_filter={"type": "decision-log"}, updated_since="2026-03-28T00:00:00Z")\n```\n\n## CLI fallback (when MCP is unavailable)\n\nIf `cerefox_search` is not in your tool list, your user has likely installed the Cerefox CLI. The canonical invocation is plain **`cerefox <subcommand>`** (the TypeScript CLI, installed via `npm install -g @cerefox/memory`). It uses a resource-verb shape (`cerefox document get`, `cerefox project list`, …).\n\nSame operations, same conventions. Full reference: [`docs/guides/cli.md`](docs/guides/cli.md). CLI flag names match MCP parameter names exactly (e.g. `metadata_filter` ↔ `--metadata-filter`); common flags also have single-letter short forms (`-f`, `-p`, `-c`, `-m`, `-u`, `-a`). Use the canonical long name (what `--help` shows) or its short form — there are no long-form aliases like `--filter` or `--count`.\n\n| MCP tool | CLI |\n|---|---|\n| `cerefox_search` | `cerefox search "<q>" --author "<your-name>"` |\n| `cerefox_ingest` (paste) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --author "<your-name>" --author-type agent` |\n| `cerefox_ingest` (update by ID) | `printf \'...\' \\| cerefox document ingest --paste --title "<t>" --document-id "<uuid>" --expected-content-hash "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_get_document` | `cerefox document get <id> --version-id <vid> --author "<your-name>"` |\n| `cerefox_list_versions` | `cerefox document version list <id> --author "<your-name>"` |\n| `cerefox_list_projects` | `cerefox project list --author "<your-name>"` |\n| `cerefox_list_metadata_keys` | `cerefox metadata keys` |\n| `cerefox_insert` | `cerefox document insert <id> -t "<text>" -p <position> -a "<anchor-heading>" -e "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_edit` | `cerefox document edit-parts <id> --operations \'<json>\' -e "<hash>" --author "<your-name>" --author-type agent` |\n| `cerefox_delete_document` | `cerefox document delete <id> --reason "<why>" --author "<your-name>" --author-type agent --yes` (confirms interactively instead of requiring the hash) |\n| `cerefox_restore_document` | `cerefox document restore <id> --reason "<why>" --author "<your-name>" --author-type agent` |\n| `cerefox_metadata_search` | `cerefox metadata search --metadata-filter \'<json>\' --author "<your-name>"` (list a project: `cerefox document list --project <name>`) |\n| `cerefox_set_document_metadata` | `cerefox document set-metadata <id> --set key=value` (also `--remove key`, `--json \'{...}\'`, `--replace`) |\n| `cerefox_set_document_projects` | `cerefox document set-projects <id> <name...> --author "<your-name>" --author-type agent` (or `--clear` to remove all) |\n| `cerefox_get_audit_log` | `cerefox audit list --author "<your-name>"` (add `--json` for scripted access) |\n| `cerefox_get_help` | `cerefox guides show agent-quick-reference` (or `cerefox guides list` for the full bundled-docs index) |\n\n**Set identity on every call**, exactly as you would on MCP:\n- Writes (`document ingest`, `document ingest-dir`): `--author "<your-name>" --author-type agent`\n- Reads: `--author "<your-name>"` (the same flag; `--requestor` still works as a hidden alias)\n\nOr have your user set `CEREFOX_AUTHOR_NAME` / `CEREFOX_AUTHOR_TYPE` / `CEREFOX_REQUESTOR_NAME` in their `.env` to apply defaults once.\n\n## Timestamps are UTC\n\nEvery timestamp Cerefox returns — `created_at` on audit entries, version\nhistory, document metadata — is **UTC**, and now carries its `Z` marker so it\ncannot be mistaken for local time.\n\n**When you write a date into a document\'s CONTENT, use your own clock, not a\nCerefox timestamp.** These are different things: a timestamp records when the\nserver stored something; a date in a log entry or a heading is authored content\nand belongs to your timezone. An agent working a Pacific afternoon read\n`2026-08-11` from version history, wrote "8/11" into its entries, and put a\nday\'s work in the future — the timestamp was correct, and copying it into\ncontent was not.\n\nCerefox deliberately does not convert to local time on the API or MCP paths.\n"Local" has no server-side meaning: the remote MCP server runs in a cloud\nfunction whose local time *is* UTC, while a local MCP server runs in yours, so\nthe same document would report two different times depending on transport. The\nweb UI converts because a browser knows the viewer\'s timezone; nothing\nserver-side does.\n\n## Mistakes that have actually happened\n\nEach of these comes from a real agent session, and each is easy to make.\n\n- **`cerefox_ingest` always replaces the ENTIRE document.** Never a section.\n Before sending, check that the tool name matches the intent: if the intent is\n "change one section", the call is `cerefox_edit` with `replace_section`. A\n section-sized edit sent as a full ingest truncated a 13,000-character index to\n a single word. It was recovered from version history within the minute, but\n only because it was noticed immediately.\n\n- **Do not include the anchor\'s own heading in your text.** `replace_section`\n keeps the heading and `insert` places your text inside the section, so\n including it produces two. This is now refused rather than silently applied,\n but the shape is worth knowing: it happened twice in one session, the second\n time while trying to repair the first. A *deeper* sub-heading inside your text\n is fine.\n\n- **Content between sections belongs to the section ABOVE it.** A section runs\n to the next heading of the same or higher level, so a `---` rule, a note, or\n any trailing text sitting just above the next heading is part of the section\n before it — even when it visually reads as belonging below. Replacing that\n section takes it too. An agent hit exactly this: a `---` that separated two\n major sections disappeared when the section above it was replaced. The write\n was correct by the addressing rules; the surprise is that "the end of this\n section" is further down the page than it looks. Note the loss warning will\n not catch it if your replacement text is longer than what it replaced, since\n there is then no net loss to report.\n\n- **To change only tags, use `cerefox_set_document_metadata`, never `cerefox_ingest`.**\n Ingest replaces the whole document, so re-sending it to set one tag carries the\n full transcription risk for no reason. The metadata tool merges: the keys you\n pass are set, everything else is left alone, so you do not need to read the\n document first and cannot drop a tag another agent set. Pass `null` as a value\n to remove a key.\n\n- **Never partial-edit to fix a partial edit.** If a write leaves unexpected\n structure, stop. Use `cerefox_list_versions`, retrieve the last good version,\n and re-ingest cleanly. Repairing edits with more edits compounds the damage.\n\n- **A rejected batch is safe.** Operations in one `cerefox_edit` are\n all-or-nothing: if any is invalid, nothing is written. A refusal costs you a\n retry, not data — so prefer one call for changes that belong together, and do\n not split a batch to "make it more likely to succeed".\n\n- **Read before replacing.** `cerefox_get_document(section: "## Heading")`\n returns exactly what a `replace_section` on that anchor would overwrite. Use it\n for any section you did not write in this session. The outline gives a\n section\'s *size*, never its *text*.\n\n- **Verify after writing** — read the result back before reporting success, and\n report what the read actually shows.\n\n- **Partial edits cannot change a document\'s stored TITLE.** `rename_section`\n changes a heading inside the content; the title is a separate field and still\n needs `cerefox_ingest`.\n\n- **If a capability seems missing from one server, suspect your client first.**\n Local and remote run the same code. **Every `cerefox_get_help()` response\n begins with the server\'s version and the operations it registers** — you do\n not need a special topic, and the *absence* of that block is itself an answer:\n a server that does not print it predates v1.5.0. If that\n disagrees with your tool list, the client is holding a list it fetched before\n an upgrade — clients cache it at connect time. Ask the user to restart the\n client. Do not record a capability difference between servers as a fact; every\n such report so far has been a stale client.\n- Long inline bodies can arrive with literal `\\n`/`\\"` (the author over-escaped; Cerefox stores bytes faithfully). For long or quote-dense content, ingest from a file or build incrementally with `cerefox_insert`; read back multi-line writes.\n', HELP_SECTIONS, HELP_SECTION_HEADINGS;
51939
52013
  var init_get_help_content = __esm(() => {
51940
52014
  HELP_SECTIONS = {
51941
- Tools: "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `author` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 10), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (omit on update to keep existing tags; `{}` clears), `author` |\n| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part`, `author` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: \"## Heading\"` one section's text | `document_id` (required), `outline`, `section`, `section_part`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type`, `author` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object), `author` |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.",
52015
+ Tools: "## Tools\n\n| Tool | Purpose | Key params |\n|------|---------|------------|\n| `cerefox_search` | Find documents (hybrid FTS + semantic) | `query` (required), `project_name`, `metadata_filter`, `author` |\n| `cerefox_ingest` | Save or update a document | `title`, `content` (required), `document_id` (update by ID), `expected_content_hash` (**required on content updates** — see rule 10), `last_write_wins`, `update_if_exists`, `project_name` (single, non-destructive add on update), `project_names` (list, destructive replace on update), `metadata` (on update it **merges**: `null` removes a key, omitted keys are kept), `expected_metadata_revision` (optional, see rule 10), `author` |\n| `cerefox_insert` | **Add** to a document without resending it. Cannot destroy content. | `document_id`, `text`, `position` (`end_of_document`/`end_of_section`/`after_heading`/`before_heading`), `expected_content_hash` (required), `anchor_heading` (unless `end_of_document`), `section_part`, `author` |\n| `cerefox_edit` | **Change** parts of a document: 1..n operations applied atomically | `document_id`, `operations` (`insert`/`replace_section`/`delete_section`/`rename_section`), `expected_content_hash` (required), `author` |\n| `cerefox_delete_document` | **Soft**-delete a document (to trash; excluded from search; permanent purge is human-only) | `document_id`, `expected_content_hash` (**required** — a delete must follow a read), `reason` (recorded in the audit log — give one), `author` |\n| `cerefox_restore_document` | Restore a soft-deleted document from the trash (audited inverse of delete; no-op if not deleted) | `document_id` (required), `reason` (recorded in the audit log), `author` |\n| `cerefox_get_document` | Get full document by ID (header includes `content_hash` — the update token), or with `outline: true` just its heading paths, sizes and hash, or with `section: \"## Heading\"` one section's text | `document_id` (required), `outline`, `section`, `section_part`, `author` |\n| `cerefox_list_versions` | Version history of a document | `document_id` (required), `author` |\n| `cerefox_set_relation` ⚑ | Link two documents (`source --rel_type--> target`) | `source_id`, `target_id`, `rel_type` (required), `metadata`, `author` |\n| `cerefox_delete_relation` ⚑ | Remove a relation | `source_id`, `target_id`, `rel_type`, `author` |\n| `cerefox_get_relations` ⚑ | All relations touching a document, both directions | `document_id` |\n| `cerefox_get_neighbors` ⚑ | Walk the graph along ONE relation type | `document_id`, `rel_type` (required), `depth`, `from_time`, `to_time`, `limit` |\n| `cerefox_metadata_search` | Find or list docs by metadata, project, or time (no text query) | `metadata_filter`, `project_name` (list a project's docs), `updated_since`, `include_content` — **at least one** of metadata_filter/project_name/updated_since/created_since |\n| `cerefox_list_metadata_keys` | Discover available metadata keys | (none required) |\n| `cerefox_list_projects` | List all projects | (none required) |\n| `cerefox_set_document_metadata` | Change tags WITHOUT resending content. **Merges** by default; a `null` value removes a key | `document_id`, `metadata` (required), `replace` (rare: set exactly this object; needs `expected_metadata_revision`), `expected_metadata_revision`, `author` |\n| `cerefox_set_document_projects` | Set doc's project memberships to exactly the given list (destructive replace; metadata-only, no content change) | `document_id`, `project_names` (required), `author` |\n| `cerefox_get_audit_log` | Query write operation history | `document_id`, `by_author` (filter), `operation`, `since`, `author` |\n| `cerefox_get_help` | Retrieve Cerefox conventions (this reference) over MCP. **Call this whenever uncertain.** | `topic` (optional, case-insensitive H2 substring match) |\n\n⚑ **Opt-in — usually absent.** The four relation tools are hidden unless the\noperator enables them (`relations_enabled`). **Trust your own tool list**: if\nthey are not in it, the feature is switched off for this deployment. That is\nnormal, not an error, and not something to work around.",
51942
52016
  "Editing part of a document (prefer this over re-sending)": '## Editing part of a document (prefer this over re-sending)\n\n**Re-sending a whole document to change part of it is the main way agents lose\ndata.** You have to reproduce the untouched remainder verbatim, and any drift\nsilently rewrites content nobody asked you to touch — which the caller cannot\ndiff. Use the partial-edit tools instead:\n\n1. **Learn the anchors** — `cerefox_get_document(document_id, outline: true)`.\n Returns heading paths, per-section sizes and the `content_hash`, without the\n body. The paths it returns are exactly what `anchor_heading` accepts.\n2. **Add** → `cerefox_insert`. `end_of_document` is a plain append;\n `end_of_section` adds inside a named section. It is structurally incapable of\n removing anything, so "I meant to append" cannot become "I replaced the file".\n3. **Look before you overwrite** — `cerefox_get_document(document_id,\n section: "## Heading")` returns exactly the text a `replace_section` on that\n anchor would destroy. The outline gives you a section\'s *size*, never its\n *text*, so on a document you did not write yourself this is the difference\n between a replace and a blind overwrite.\n4. **Change or remove** → `cerefox_edit`. Put changes that belong together in\n ONE call: they apply atomically, so a table row and the total it feeds cannot\n end up disagreeing. To change a single line, `replace_section` on its\n smallest enclosing heading — that is the intended granularity, not a\n workaround. To fix a stale heading (`## OPEN TODOs (as of ...)`), use\n `rename_section`: it changes the heading text and leaves the body and\n position alone.\n5. All of them require `expected_content_hash` and **have no last-write-wins**. A\n conflict means someone else changed the document; re-read and decide, do not\n force it.\n\n**A section runs to the next same-or-higher heading, or to the end of the\ndocument.** So `end_of_document` inserts land inside the *last* section, and\nreplacing or deleting that section removes them too. A large shrink in the\nresponse is your warning; `cerefox_list_versions` has the previous content.\n\n**When an anchor is ambiguous the tool refuses and hands you the options** — a\nrepeated heading returns the qualifying paths, and a section with both its own\ncontent and sub-sections returns both `section_part` choices. That is a\nrecoverable answer, not a failure: retry with what it gave you.',
51943
- "Essential Rules": '## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`** to your name on every call, reads and writes alike (e.g., "Claude Code", "archiver"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never "No results found." — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n7. **Write for retrieval: the title does real work.** The document title is indexed at the **highest weight** *and* prepended to every chunk before it is embedded, so it steers both halves of hybrid search (keyword and semantic) for every chunk in the document. Name the document with the distinctive terms someone would actually search for -- "Cerefox Response Size Limits", not "Notes" or "Update 3". Renaming re-indexes and re-embeds, so correcting a vague title is cheap and takes effect immediately. Write the characters themselves: `&` and `<>`, never `&amp;` or `&lt;&gt;` (entities are stored literally and the write reply will say so). Inside the content, write structured Markdown with H1/H2/H3 headings: the chunker follows them, and a chunk\'s own heading carries the same weight as the title.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document\'s `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good; so is one auto-purged after `trash_retention_days`, if the operator has turned trash auto-purge on.\n9. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict.\n11. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n12. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behavior. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n13. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.',
52017
+ "Essential Rules": '## Essential Rules\n\n1. **Search before ingesting** -- check if the document exists first.\n2. **Prefer ID-based updates** -- pass `document_id` from search results for deterministic updates. Falls back to title-matching with `update_if_exists: true`.\n3. **Set `author`** to your name on every call, reads and writes alike (e.g., "Claude Code", "archiver"). Same parameter on every tool. (`requestor` is still accepted everywhere as the pre-1.13.1 alias.) On the CLI it is `--author` on every command too (plus `--author-type` on writes); or rely on `CEREFOX_AUTHOR_NAME`/`CEREFOX_AUTHOR_TYPE` env vars set in the user\'s `.env`.\n4. **Use `document_id` from search results** `[id: uuid]` for get_document and list_versions.\n5. **`max_bytes` has to fit a whole document.** Search returns COMPLETE documents, so a budget smaller than the top hit fits nothing. You get a header-only list saying so (never "No results found." — that means the store really has nothing). Raise `max_bytes`, or read one document with `cerefox_get_document` using `outline: true` or `section`.\n6. **Add metadata** -- at minimum `type` ("decision-log", "research", "design-doc") and `status` ("active", "draft").\n7. **Write for retrieval: the title does real work.** The document title is indexed at the **highest weight** *and* prepended to every chunk before it is embedded, so it steers both halves of hybrid search (keyword and semantic) for every chunk in the document. Name the document with the distinctive terms someone would actually search for -- "Cerefox Response Size Limits", not "Notes" or "Update 3". Renaming re-indexes and re-embeds, so correcting a vague title is cheap and takes effect immediately. Write the characters themselves: `&` and `<>`, never `&amp;` or `&lt;&gt;` (entities are stored literally and the write reply will say so). Inside the content, write structured Markdown with H1/H2/H3 headings: the chunker follows them, and a chunk\'s own heading carries the same weight as the title.\n8. **Deletes are soft (recoverable); permanent purge is web-UI-only.** `cerefox_delete_document` requires the document\'s `content_hash` as you read it (read before you delete) and takes a `reason` — give one; it is what the human reviewing the trash sees. `cerefox_restore_document` undoes a mistaken delete (also audited, also takes a `reason`). Always surface deletes AND restores to the user. Once a human purges from the web UI, the document is gone for good; so is one auto-purged after `trash_retention_days`, if the operator has turned trash auto-purge on.\n9. **Cross-doc links inside content**: **always use `[Text](document-uuid)`.** UUIDs are the only fully reliable link form — stable across title changes, never ambiguous, no encoding gotchas. Every `cerefox_search` result shows `[id: <uuid>]` after the title; grab it and use it. Title-based linking (`[Text](<Title With Spaces>)`) is fragile (breaks on colons, parens, ampersands, brackets — silently navigates to wrong page) — **don\'t write title-based links**; do an extra search to get the UUID instead. Repo-path forms (`[Text](docs/path.md)`) exist for repo-ingested files; don\'t construct manually. **The server validates `](uuid)` links on every write** (v1.7.0): a link to a nonexistent id rejects the write, naming the offender — that means you mangled the UUID; re-read the source and correct it, do not retry unchanged. Example ids go in backticks (code is not validated). `[[Wikilinks]]` may dangle. See `AGENT_GUIDE.md → Writing linkable content` for the full rule.\n10. **Concurrency: content updates require `expected_content_hash`.** Pass the `content_hash` you last saw — every read shows one (`cerefox_get_document` incl. outline mode, `cerefox_search`, `cerefox_metadata_search`) and **every write returns the new one, including create** (v1.3.0, #189), so after writing you already hold the token for your next edit; no re-read needed. If it\'s stale you get a **conflict** — re-read the document, merge your changes into the latest content, retry with the new hash. **Never resolve a conflict by overwriting blindly** — the current content includes another writer\'s work. `last_write_wins: true` skips the check; use it ONLY when an external source of truth makes conflicts meaningless (file re-sync), never to silence a conflict. **Metadata has its own token, `metadata_revision`** (shown next to `content_hash` wherever it appears). It is optional on a merge (pass it when your change depends on the current values, e.g. a status transition) and **required with `replace: true`**. A stale one is a *metadata conflict*: re-read, reapply, retry.\n11. **Search: prefer a few distinctive terms; heed `below confidence`.** When nothing clears the relevance threshold, `cerefox_search` returns the closest candidates prefixed with a `below confidence` warning instead of an empty set — that flag means **weak signal, not absent knowledge**: check the candidates\' scores and titles before concluding the KB lacks the content. A truly empty response means nothing even weakly related exists.\n12. **Relations express how documents relate; lifecycle tells you if knowledge is still good.** Use `cerefox_set_relation` when one document supersedes, contradicts, references, or continues another. `supersedes` marks the target **superseded**; `contradicts` marks **both** stale; `related_to`/`duplicates`/`contradicts` are symmetric (both directions written). Any other type string is accepted without special behavior. When a search result or `cerefox_get_relations` shows a neighbour marked `[superseded]` or `[stale]`, say so rather than presenting it as current.\n13. **Project memberships — non-destructive by default**: on `cerefox_ingest` updates, **`project_name` (singular) is a non-destructive add** (ensures membership, preserves others). Use **`project_names` (list)** when you want to set the doc\'s full project set in one call (destructive replace). For metadata-only project changes without writing content, use **`cerefox_set_document_projects(document_id, project_names)`** — that tool is the destructive-replace contract made explicit. Never call `cerefox_set_document_projects` with a single name when you mean "add" — that would REMOVE the doc from all other projects. When in doubt, use `cerefox_ingest` with singular `project_name`.',
51944
52018
  "Update Workflow (ID-based -- preferred)": `## Update Workflow (ID-based -- preferred)
51945
52019
 
51946
52020
  \`\`\`
@@ -52152,6 +52226,9 @@ function conflictError3(documentId, expectedHash, currentHash) {
52152
52226
  return new Error(`Conflict: document ${documentId} changed since you read it ` + `(your base hash: ${expectedHash}, current hash: ${currentHash}). ` + `To resolve: (1) cerefox_get_document("${documentId}") to fetch the latest content ` + `and its content_hash, (2) merge your changes into it, (3) retry cerefox_ingest ` + `with expected_content_hash set to the new hash. Do not overwrite blindly — ` + `the current content may include another writer's work.`);
52153
52227
  }
52154
52228
  function mapIngestRpcError(message, documentId) {
52229
+ const metaErr = metadataTokenError(message);
52230
+ if (metaErr)
52231
+ return metaErr;
52155
52232
  if (message.includes("CEREFOX_CONFLICT")) {
52156
52233
  const { expected, current } = extractConflictHashes(message);
52157
52234
  return conflictError3(documentId, expected, current);
@@ -52168,7 +52245,7 @@ function mapIngestRpcError(message, documentId) {
52168
52245
  const current = message.match(/Current hash: ([0-9a-f]{64})/)?.[1];
52169
52246
  return new Error(`Concurrency token required: content updates need expected_content_hash — ` + `the content_hash of the version you based your edit on (returned by ` + `cerefox_get_document, cerefox_search, and cerefox_metadata_search).` + (current ? ` The document's current hash is ${current}; pass it ONLY if your edit was based on the current content.` : "") + ` If you have not read the document, read it first. To deliberately overwrite ` + `regardless of concurrent changes, pass last_write_wins=true.`);
52170
52247
  }
52171
- return new Error(`Ingest RPC failed: ${message}`);
52248
+ return legacyContentUniqueError(message) ?? new Error(`Ingest RPC failed: ${message}`);
52172
52249
  }
52173
52250
  async function handler6(supabase, args, ctx) {
52174
52251
  const title = args.title?.trim();
@@ -52187,6 +52264,11 @@ async function handler6(supabase, args, ctx) {
52187
52264
  const author_type = "agent";
52188
52265
  const expected_content_hash = args.expected_content_hash?.trim() || null;
52189
52266
  const last_write_wins = args.last_write_wins ?? false;
52267
+ const expectedMetaRaw = args.expected_metadata_revision;
52268
+ if (expectedMetaRaw !== undefined && expectedMetaRaw !== null && !(typeof expectedMetaRaw === "number" && Number.isInteger(expectedMetaRaw))) {
52269
+ throw new McpInvalidParams("expected_metadata_revision must be an integer (the metadata_revision you read)");
52270
+ }
52271
+ const expected_metadata_revision = expectedMetaRaw ?? null;
52190
52272
  if (!title || !content?.trim()) {
52191
52273
  throw new McpInvalidParams("title and content are required");
52192
52274
  }
@@ -52242,7 +52324,8 @@ async function handler6(supabase, args, ctx) {
52242
52324
  p_source_label: source,
52243
52325
  p_expected_content_hash: expected_content_hash,
52244
52326
  p_last_write_wins: last_write_wins,
52245
- p_content_format: CONTENT_FORMAT_BLIND_STITCH
52327
+ p_content_format: CONTENT_FORMAT_BLIND_STITCH,
52328
+ ...metadataTokenParams(expected_metadata_revision, false)
52246
52329
  });
52247
52330
  if (ingestErr)
52248
52331
  throw mapIngestRpcError(ingestErr.message, existingDoc.id);
@@ -52305,7 +52388,8 @@ async function handler6(supabase, args, ctx) {
52305
52388
  p_source_label: source,
52306
52389
  p_expected_content_hash: expected_content_hash,
52307
52390
  p_last_write_wins: last_write_wins,
52308
- p_content_format: CONTENT_FORMAT_BLIND_STITCH
52391
+ p_content_format: CONTENT_FORMAT_BLIND_STITCH,
52392
+ ...metadataTokenParams(expected_metadata_revision, false)
52309
52393
  });
52310
52394
  if (ingestErr)
52311
52395
  throw mapIngestRpcError(ingestErr.message, existingDoc.id);
@@ -52324,12 +52408,9 @@ async function handler6(supabase, args, ctx) {
52324
52408
  return `Document updated: "${existingDoc.title}" (id: ${existingDoc.id}), ${chunks.length} chunk(s), ${totalChars} chars. New content_hash: ${contentHash}.${escapedContentNote(content)}${escapedTitleNote(title)}`;
52325
52409
  }
52326
52410
  }
52327
- const { data: hashMatch } = await supabase.from("cerefox_documents").select("id, title, deleted_at").eq("content_hash", contentHash).limit(1);
52411
+ const { data: hashMatch } = await supabase.from("cerefox_documents").select("id, title").eq("content_hash", contentHash).is("deleted_at", null).order("created_at", { ascending: true }).limit(1);
52328
52412
  if (hashMatch?.length) {
52329
- if (hashMatch[0].deleted_at) {
52330
- return `This exact content is already in the TRASH as "${hashMatch[0].title}" ` + `(soft-deleted, id: ${hashMatch[0].id}). Restore it with ` + `cerefox_restore_document instead of re-ingesting — or have your user ` + `purge it from the web UI Trash to start fresh.`;
52331
- }
52332
- return `Document already up-to-date: "${hashMatch[0].title}" (id: ${hashMatch[0].id}). Content hash unchanged (${contentHash}) — pass it as expected_content_hash to edit it.`;
52413
+ return alreadyExistsMessage(hashMatch[0].title, hashMatch[0].id, contentHash);
52333
52414
  }
52334
52415
  const chunks = chunkMarkdown(content);
52335
52416
  if (chunks.length === 0)
@@ -52358,7 +52439,15 @@ async function handler6(supabase, args, ctx) {
52358
52439
  p_author_type: author_type,
52359
52440
  p_content_format: CONTENT_FORMAT_BLIND_STITCH
52360
52441
  });
52442
+ const duplicateId = duplicateContentDocumentId(ingestErr?.message ?? "");
52443
+ if (duplicateId) {
52444
+ const { data: existing } = await supabase.from("cerefox_documents").select("title").eq("id", duplicateId).maybeSingle();
52445
+ return alreadyExistsMessage(existing?.title ?? title, duplicateId, contentHash);
52446
+ }
52361
52447
  if (ingestErr || !ingestResult?.length) {
52448
+ const legacy = legacyContentUniqueError(ingestErr?.message ?? "");
52449
+ if (legacy)
52450
+ throw legacy;
52362
52451
  throw new Error(`Ingest RPC failed: ${ingestErr?.message ?? "no data returned"}`);
52363
52452
  }
52364
52453
  const documentId = ingestResult[0].document_id;
@@ -52377,6 +52466,9 @@ async function handler6(supabase, args, ctx) {
52377
52466
  const projectInfo = project_name ? `, project: "${project_name}"` : "";
52378
52467
  return `Document saved: "${title}" (id: ${documentId}), ${chunks.length} chunk(s), ${totalChars} chars${projectInfo}. ` + `content_hash: ${contentHash} — pass it as expected_content_hash on your next edit.` + escapedContentNote(content) + escapedTitleNote(title);
52379
52468
  }
52469
+ function alreadyExistsMessage(title, id, contentHash) {
52470
+ return `Document already up-to-date: "${title}" (id: ${id}). Content hash unchanged (${contentHash}) — pass it as expected_content_hash to edit it.`;
52471
+ }
52380
52472
  var ingestTool;
52381
52473
  var init_ingest = __esm(() => {
52382
52474
  init_escape_heuristic();
@@ -52427,7 +52519,14 @@ var init_ingest = __esm(() => {
52427
52519
  type: "boolean",
52428
52520
  description: "Explicitly skip the concurrency check and overwrite regardless of concurrent changes (default: false). Use ONLY when an external source of truth makes conflicts meaningless (e.g. re-syncing from files). Recorded in the audit log."
52429
52521
  },
52430
- metadata: { type: "object", description: "Arbitrary JSON metadata (optional)" },
52522
+ metadata: {
52523
+ type: "object",
52524
+ description: "JSON metadata (optional). On an UPDATE it is MERGED into the existing metadata: keys you pass are set, a null value removes that key, keys you omit are kept. To change only metadata, use cerefox_set_document_metadata instead."
52525
+ },
52526
+ expected_metadata_revision: {
52527
+ type: "integer",
52528
+ description: "Optional, with metadata on an update: the metadata_revision you read (cerefox_get_document / cerefox_metadata_search). The write fails if the metadata changed since."
52529
+ },
52431
52530
  author: AUTHOR_PARAM_WRITE
52432
52531
  }
52433
52532
  },
@@ -52666,8 +52765,9 @@ ${lines.join(`
52666
52765
  const parts = rows.map((row) => {
52667
52766
  const projects = row.project_names?.length ? ` | projects: ${row.project_names.join(", ")}` : "";
52668
52767
  const meta = Object.entries(row.doc_metadata ?? {}).map(([k, v]) => `${k}=${v}`).join(", ");
52768
+ const rev = row.metadata_revision != null ? ` · metadata_revision: ${row.metadata_revision}` : "";
52669
52769
  const hash = row.content_hash ? `
52670
- hash: ${row.content_hash}` : "";
52770
+ hash: ${row.content_hash}${rev}` : "";
52671
52771
  const header = `## ${row.title} [id: ${row.document_id}]
52672
52772
  ` + `${meta}${projects} | ${row.total_chars} chars | ${showReview ? `${row.review_status} | ` : ""}updated ${row.updated_at?.slice(0, 10) ?? "?"}${hash}`;
52673
52773
  if (include_content && row.content) {
@@ -52766,12 +52866,18 @@ function shortLabel(row) {
52766
52866
  const id = row.document_id ? ` [id: ${row.document_id}]` : "";
52767
52867
  return `${leaf ? `${doc} › ${leaf}` : doc}${chunk}${id}`;
52768
52868
  }
52869
+ function hashLine(row) {
52870
+ if (!row.content_hash)
52871
+ return "";
52872
+ const rev = row.metadata_revision != null ? ` · metadata_revision: ${row.metadata_revision}` : "";
52873
+ return `
52874
+ hash: ${row.content_hash}${rev}`;
52875
+ }
52769
52876
  function renderRow(row) {
52770
52877
  const raw = row.best_score ?? row.score;
52771
52878
  const score = raw != null ? ` (score: ${raw.toFixed(3)})` : "";
52772
52879
  const partial = row.is_partial ? ` -- partial (${row.chunk_count} of ${(row.total_chars ?? 0).toLocaleString()} chars)` : "";
52773
- const hash = row.content_hash ? `
52774
- hash: ${row.content_hash}` : "";
52880
+ const hash = hashLine(row);
52775
52881
  return `## ${rowHeading(row)}${score}${partial}${hash}
52776
52882
 
52777
52883
  ${rowContent(row)}`;
@@ -52780,8 +52886,7 @@ function headerLine(row) {
52780
52886
  const raw = row.best_score ?? row.score;
52781
52887
  const score = raw != null ? ` (score: ${raw.toFixed(3)})` : "";
52782
52888
  const size = row.total_chars != null ? ` -- ${row.total_chars.toLocaleString()} chars` : "";
52783
- const hash = row.content_hash ? `
52784
- hash: ${row.content_hash}` : "";
52889
+ const hash = hashLine(row);
52785
52890
  return `## ${rowHeading(row)}${score}${size}${hash}`;
52786
52891
  }
52787
52892
  function degradedToHeaders(matched, rendered, maxBytes, belowConfidence) {
@@ -53043,6 +53148,11 @@ async function handler12(supabase, args, ctx) {
53043
53148
  const document_id = args.document_id;
53044
53149
  const metadata = args.metadata;
53045
53150
  const replace = args.replace ?? false;
53151
+ const expectedRevision = args.expected_metadata_revision;
53152
+ if (expectedRevision !== undefined && expectedRevision !== null && !(typeof expectedRevision === "number" && Number.isInteger(expectedRevision))) {
53153
+ throw new McpInvalidParams("expected_metadata_revision must be an integer (the metadata_revision you read)");
53154
+ }
53155
+ const lastWriteWins = args.last_write_wins ?? false;
53046
53156
  if (!document_id)
53047
53157
  throw new McpInvalidParams("document_id is required");
53048
53158
  if (metadata === undefined || metadata === null) {
@@ -53061,10 +53171,11 @@ async function handler12(supabase, args, ctx) {
53061
53171
  p_metadata: metadata,
53062
53172
  p_replace: replace,
53063
53173
  p_author: author ?? "unknown",
53064
- p_author_type: authorType
53174
+ p_author_type: authorType,
53175
+ ...metadataTokenParams(expectedRevision, lastWriteWins)
53065
53176
  });
53066
53177
  if (error)
53067
- throw new Error(`RPC error: ${error.message}`);
53178
+ throw metadataTokenError(error.message) ?? new Error(`RPC error: ${error.message}`);
53068
53179
  const row = data?.[0];
53069
53180
  if (!row)
53070
53181
  throw new Error("cerefox_set_document_metadata returned no data");
@@ -53080,7 +53191,8 @@ async function handler12(supabase, args, ctx) {
53080
53191
  const summary = set === 0 && removed === 0 ? "No change: every key already held that value." : `${set} key(s) set, ${removed} removed.`;
53081
53192
  return `Metadata ${replace ? "replaced" : "merged"} on ${document_id}. ${summary}
53082
53193
  ` + `Now: ${JSON.stringify(row.metadata ?? {})}
53083
- ` + `Content untouched — no new version, no re-embedding.`;
53194
+ ` + (row.metadata_revision != null ? `metadata_revision: ${row.metadata_revision}
53195
+ ` : "") + `Content untouched — no new version, no re-embedding.`;
53084
53196
  }
53085
53197
  var setDocumentMetadataTool;
53086
53198
  var init_set_document_metadata = __esm(() => {
@@ -53089,7 +53201,7 @@ var init_set_document_metadata = __esm(() => {
53089
53201
  init_identity();
53090
53202
  setDocumentMetadataTool = {
53091
53203
  name: "cerefox_set_document_metadata",
53092
- description: `Change a document's metadata WITHOUT resending its content. MERGES by default: the keys you pass are set, every other key is left alone — so you do not need to read the document first, and you cannot accidentally drop tags another agent set. To REMOVE a key, pass it with a null value ({"stale_key": null}). Pass replace: true to set the metadata to exactly the object given, discarding everything else (rare; the same destructive contract as cerefox_set_document_projects). Content, chunks and embeddings are untouched and no new version is created. Use this instead of cerefox_ingest whenever only the tags are changing.`,
53204
+ description: `Change a document's metadata WITHOUT resending its content. MERGES by default: the keys you pass are set, every other key is left alone — so you do not need to read the document first, and you cannot accidentally drop tags another agent set. To REMOVE a key, pass it with a null value ({"stale_key": null}). Pass replace: true to set the metadata to exactly the object given, discarding everything else (rare; it requires expected_metadata_revision or last_write_wins). To make a merge fail if anyone changed the metadata since you read it, pass expected_metadata_revision. Content, chunks and embeddings are untouched and no new version is created. Use this instead of cerefox_ingest whenever only the tags are changing.`,
53093
53205
  annotations: {
53094
53206
  title: "Set document metadata",
53095
53207
  readOnlyHint: false,
@@ -53108,7 +53220,15 @@ var init_set_document_metadata = __esm(() => {
53108
53220
  },
53109
53221
  replace: {
53110
53222
  type: "boolean",
53111
- description: "Set the metadata to EXACTLY this object, discarding any key not listed. Defaults to false (merge). Use only when you mean to reset a document's tags wholesale."
53223
+ description: "Set the metadata to EXACTLY this object, discarding any key not listed. Defaults to false (merge). Use only when you mean to reset a document's tags wholesale. Requires expected_metadata_revision (or last_write_wins)."
53224
+ },
53225
+ expected_metadata_revision: {
53226
+ type: "integer",
53227
+ description: "The metadata_revision you read (from cerefox_get_document or cerefox_metadata_search). Optional on a merge: when given, the write fails if the metadata changed since. Required with replace: true."
53228
+ },
53229
+ last_write_wins: {
53230
+ type: "boolean",
53231
+ description: "Replace without a metadata_revision, regardless of concurrent changes (default false). Recorded in the audit log. Use only when an external source of truth makes conflicts meaningless."
53112
53232
  },
53113
53233
  author: AUTHOR_PARAM_WRITE
53114
53234
  }
@@ -68631,6 +68751,7 @@ function registerDocumentRestore(parent) {
68631
68751
  // src/cli/commands/document-set-metadata.ts
68632
68752
  init_cli_core();
68633
68753
  init_client();
68754
+ init__utils();
68634
68755
  async function action13(documentId, options) {
68635
68756
  const patch = {};
68636
68757
  if (options.json) {
@@ -68669,6 +68790,13 @@ async function action13(documentId, options) {
68669
68790
  if (Object.keys(patch).length === 0 && !options.replace) {
68670
68791
  throw userError("Nothing to change. Pass --set key=value, --remove key, or --json '{...}'. " + "To clear all metadata use --replace --json '{}'.");
68671
68792
  }
68793
+ let expectedRevision = null;
68794
+ if (options.expectedMetadataRevision !== undefined) {
68795
+ expectedRevision = Number(options.expectedMetadataRevision);
68796
+ if (!Number.isInteger(expectedRevision)) {
68797
+ throw userError("--expected-metadata-revision must be an integer (the metadata_revision you read).");
68798
+ }
68799
+ }
68672
68800
  const client = getClient();
68673
68801
  let rows;
68674
68802
  try {
@@ -68677,12 +68805,19 @@ async function action13(documentId, options) {
68677
68805
  p_metadata: patch,
68678
68806
  p_replace: Boolean(options.replace),
68679
68807
  p_author: resolveAuthor(options.author),
68680
- p_author_type: options.authorType === "agent" ? "agent" : "user"
68808
+ p_author_type: options.authorType === "agent" ? "agent" : "user",
68809
+ ...metadataTokenParams(expectedRevision, options.lastWriteWins)
68681
68810
  });
68682
68811
  } catch (err) {
68683
68812
  const msg = err instanceof Error ? err.message : String(err);
68684
68813
  if (/not found/i.test(msg))
68685
68814
  throw notFound(`Document ${documentId} not found (or is deleted).`);
68815
+ if (msg.includes("CEREFOX_METADATA_CONFLICT")) {
68816
+ throw userError(`The metadata of ${documentId} changed since you read it. Re-read it ` + `(cerefox document get ${documentId} --outline), reapply your change, and retry ` + `with the new --expected-metadata-revision.`);
68817
+ }
68818
+ if (msg.includes("CEREFOX_METADATA_TOKEN_REQUIRED")) {
68819
+ throw userError(`--replace discards every key you do not list, so it needs --expected-metadata-revision ` + `(from cerefox document get ${documentId} --outline) or --last-write-wins.`);
68820
+ }
68686
68821
  throw systemError(`Could not set metadata: ${msg}`);
68687
68822
  }
68688
68823
  if (!rows || rows.length === 0) {
@@ -68700,10 +68835,12 @@ async function action13(documentId, options) {
68700
68835
  println(c.green(`✓ ${verb} metadata on ${documentId}: ${row.keys_set} key(s) set, ${row.keys_removed} removed.`));
68701
68836
  }
68702
68837
  println(c.dim(` Now: ${JSON.stringify(row.metadata ?? {})}`));
68838
+ if (row.metadata_revision != null)
68839
+ println(c.dim(` metadata_revision: ${row.metadata_revision}`));
68703
68840
  println(c.dim(" Content untouched — no new version, no re-embedding."));
68704
68841
  }
68705
68842
  function registerDocumentSetMetadata(parent) {
68706
- parent.command("set-metadata").description("Change a document's metadata without resending its content (merges by default).").argument("<document-id>", "UUID of the document.").option("-s, --set <key=value...>", "Set a key. Repeatable. Values are stored as JSON strings; quote to force JSON parsing.").option("-r, --remove <key...>", "Remove a key. Repeatable. (Sends a JSON null.)").option("--json <object>", "A JSON object of keys to set; a null value removes that key.").option("--replace", "Set the metadata to EXACTLY what was given, discarding every key not listed. Default is merge.").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("--json-out", "Emit the result as JSON.").action(action13);
68843
+ parent.command("set-metadata").description("Change a document's metadata without resending its content (merges by default).").argument("<document-id>", "UUID of the document.").option("-s, --set <key=value...>", "Set a key. Repeatable. Values are stored as JSON strings; quote to force JSON parsing.").option("-r, --remove <key...>", "Remove a key. Repeatable. (Sends a JSON null.)").option("--json <object>", "A JSON object of keys to set; a null value removes that key.").option("--replace", "Set the metadata to EXACTLY what was given, discarding every key not listed. Default is merge. Needs --expected-metadata-revision or --last-write-wins.").option("--expected-metadata-revision <n>", "The metadata_revision you read (document get). Fails if the metadata changed since. Required with --replace.").option("--last-write-wins", "Replace without --expected-metadata-revision (recorded in the audit log).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").option("--json-out", "Emit the result as JSON.").action(action13);
68707
68844
  }
68708
68845
 
68709
68846
  // src/cli/commands/document-set-projects.ts
@@ -73338,7 +73475,8 @@ async function action18(options) {
73338
73475
  operation: row.operation,
73339
73476
  doc: auditDocLabel(row.doc_title, row.document_id, row.operation).slice(0, 40),
73340
73477
  author: (row.author ?? "") + (row.author_type ? `(${row.author_type})` : ""),
73341
- size_delta: row.size_before !== null && row.size_after !== null ? `${row.size_before} → ${row.size_after}` : ""
73478
+ size_delta: row.size_before !== null && row.size_after !== null ? `${row.size_before} → ${row.size_after}` : "",
73479
+ change: (metadataChangeSummary(row.metadata_before, row.metadata_after) ?? "").slice(0, 60)
73342
73480
  })));
73343
73481
  }
73344
73482
  function registerGetAuditLog(program) {
@@ -73478,6 +73616,9 @@ init_cli_core();
73478
73616
  init_cli_core();
73479
73617
  init_partial_edits();
73480
73618
  init_client();
73619
+ function revisionSuffix(doc) {
73620
+ return !doc.version_id && doc.metadata_revision != null ? ` · metadata_revision: ${doc.metadata_revision}` : "";
73621
+ }
73481
73622
  async function action19(documentId, options) {
73482
73623
  const section = (options.section ?? "").trim() || null;
73483
73624
  if (section && options.outline) {
@@ -73511,6 +73652,7 @@ async function action19(documentId, options) {
73511
73652
  printJson({
73512
73653
  title: doc.doc_title,
73513
73654
  content_hash: doc.content_hash,
73655
+ metadata_revision: doc.metadata_revision ?? null,
73514
73656
  total_chars: doc.total_chars,
73515
73657
  outline: nodes.map((n) => ({ path: n.path, level: n.level, chars: n.chars }))
73516
73658
  });
@@ -73519,7 +73661,7 @@ async function action19(documentId, options) {
73519
73661
  println(c.bold(`# ${doc.doc_title}`));
73520
73662
  println(c.dim(`[${doc.document_id}] · chars: ${doc.total_chars}`));
73521
73663
  if (doc.content_hash)
73522
- println(c.dim(`content_hash: ${doc.content_hash}`));
73664
+ println(c.dim(`content_hash: ${doc.content_hash}${revisionSuffix(doc)}`));
73523
73665
  println("");
73524
73666
  if (nodes.length === 0) {
73525
73667
  println(c.dim("No headings: only --position end_of_document applies to this document."));
@@ -73569,7 +73711,7 @@ async function action19(documentId, options) {
73569
73711
  println(c.bold(`# ${doc.doc_title}`));
73570
73712
  println(c.dim(`[${doc.document_id}] · chunks: ${doc.chunk_count} · chars: ${doc.total_chars}` + (doc.is_archived ? " · archived" : "") + (doc.version_id ? ` · version: ${doc.version_id}` : "")));
73571
73713
  if (doc.content_hash) {
73572
- println(c.dim(`content_hash: ${doc.content_hash}`));
73714
+ println(c.dim(`content_hash: ${doc.content_hash}${revisionSuffix(doc)}`));
73573
73715
  }
73574
73716
  println("");
73575
73717
  println(doc.full_content);
@@ -73637,6 +73779,8 @@ init__projects();
73637
73779
  init__utils();
73638
73780
 
73639
73781
  // src/ingestion/types.ts
73782
+ init__utils();
73783
+
73640
73784
  class ConcurrencyConflictError extends Error {
73641
73785
  documentId;
73642
73786
  currentHash;
@@ -73688,7 +73832,7 @@ class IngestionDbBridge {
73688
73832
  return data ?? null;
73689
73833
  }
73690
73834
  async getDocumentByHash(contentHash) {
73691
- const { data } = await this.supabase.from("cerefox_documents").select("*").eq("content_hash", contentHash).limit(1);
73835
+ const { data } = await this.supabase.from("cerefox_documents").select("*").eq("content_hash", contentHash).is("deleted_at", null).order("created_at", { ascending: true }).limit(1);
73692
73836
  const rows = data ?? [];
73693
73837
  return rows.length > 0 ? rows[0] : null;
73694
73838
  }
@@ -73715,6 +73859,24 @@ class IngestionDbBridge {
73715
73859
  const { data } = await this.supabase.from("cerefox_document_projects").select("project_id").eq("document_id", documentId);
73716
73860
  return (data ?? []).map((r) => r.project_id);
73717
73861
  }
73862
+ async setDocumentMetadataRpc(args) {
73863
+ const { data, error } = await this.supabase.rpc("cerefox_set_document_metadata", {
73864
+ p_document_id: args.documentId,
73865
+ p_metadata: args.metadata,
73866
+ p_replace: args.replace,
73867
+ p_author: args.author,
73868
+ p_author_type: args.authorType,
73869
+ ...metadataTokenParams(args.expectedMetadataRevision, args.lastWriteWins)
73870
+ });
73871
+ if (error) {
73872
+ const msg = error.message ?? JSON.stringify(error);
73873
+ if (msg.includes("CEREFOX_METADATA_"))
73874
+ throw new MetadataConcurrencyError(msg);
73875
+ throw new Error(msg);
73876
+ }
73877
+ const row = Array.isArray(data) ? data[0] : data;
73878
+ return row ?? {};
73879
+ }
73718
73880
  async ingestDocumentRpc(args) {
73719
73881
  const params = {
73720
73882
  p_document_id: args.documentId,
@@ -73738,6 +73900,11 @@ class IngestionDbBridge {
73738
73900
  if (args.documentId !== null) {
73739
73901
  params.p_expected_content_hash = args.expectedContentHash ?? null;
73740
73902
  params.p_last_write_wins = args.lastWriteWins ?? false;
73903
+ if (args.expectedMetadataRevision != null) {
73904
+ params.p_expected_metadata_revision = args.expectedMetadataRevision;
73905
+ }
73906
+ if (args.replaceMetadata)
73907
+ params.p_replace_metadata = true;
73741
73908
  }
73742
73909
  const { data, error } = await this.supabase.rpc("cerefox_ingest_document", params);
73743
73910
  if (error) {
@@ -73749,7 +73916,12 @@ class IngestionDbBridge {
73749
73916
  if (msg.includes("CEREFOX_TOKEN_REQUIRED")) {
73750
73917
  throw new ConcurrencyTokenRequiredError(msg);
73751
73918
  }
73752
- throw new Error(msg);
73919
+ if (msg.includes("CEREFOX_METADATA_"))
73920
+ throw new MetadataConcurrencyError(msg);
73921
+ const duplicateId = duplicateContentDocumentId(msg);
73922
+ if (duplicateId)
73923
+ throw new DuplicateContentError(duplicateId, msg);
73924
+ throw legacyContentUniqueError(msg) ?? new Error(msg);
73753
73925
  }
73754
73926
  if (Array.isArray(data) && data.length > 0) {
73755
73927
  return data[0];
@@ -73913,6 +74085,7 @@ async function fileToMarkdown(filename, data) {
73913
74085
  }
73914
74086
 
73915
74087
  // src/ingestion/pipeline.ts
74088
+ init__utils();
73916
74089
  function updateByIdNote(updateExisting) {
73917
74090
  return updateExisting === false ? "document_id provided; update_if_exists flag was overridden" : undefined;
73918
74091
  }
@@ -73947,7 +74120,9 @@ class IngestionPipeline {
73947
74120
  authorType = "user",
73948
74121
  expectedContentHash,
73949
74122
  lastWriteWins = false,
73950
- forceRechunk = false
74123
+ forceRechunk = false,
74124
+ expectedMetadataRevision = null,
74125
+ replaceMetadata = false
73951
74126
  } = opts;
73952
74127
  const listFormProvided = projectIds !== undefined && projectIds !== null || projectNames !== undefined && projectNames !== null;
73953
74128
  const getOrCreate = (name) => this.db.getOrCreateProject(name, { author, authorType });
@@ -73972,7 +74147,9 @@ class IngestionPipeline {
73972
74147
  authorType,
73973
74148
  expectedContentHash,
73974
74149
  lastWriteWins,
73975
- forceRechunk
74150
+ forceRechunk,
74151
+ expectedMetadataRevision,
74152
+ replaceMetadata
73976
74153
  });
73977
74154
  if (!listFormProvided && (projectId || projectName)) {
73978
74155
  const singular = await resolveProjectIds({ projectId, projectName }, getOrCreate);
@@ -74031,7 +74208,9 @@ class IngestionPipeline {
74031
74208
  author,
74032
74209
  authorType,
74033
74210
  expectedContentHash,
74034
- lastWriteWins
74211
+ lastWriteWins,
74212
+ expectedMetadataRevision,
74213
+ replaceMetadata
74035
74214
  });
74036
74215
  if (!listFormProvided && (projectId || projectName)) {
74037
74216
  const singular = await resolveProjectIds({ projectId, projectName }, getOrCreate);
@@ -74057,7 +74236,7 @@ class IngestionPipeline {
74057
74236
  action: "skipped",
74058
74237
  reindexed: false,
74059
74238
  projectIds: existingProjectIds,
74060
- note: existingByHash.deleted_at ? `Identical content is in the TRASH as "${existingByHash.title}" ` + `(soft-deleted ${existingByHash.deleted_at.slice(0, 10)}). Restore it ` + `(\`cerefox document restore ${existingByHash.id}\` or the web UI Trash page) ` + `instead of re-ingesting, or purge it first to start fresh.` : "",
74239
+ note: "",
74061
74240
  contentHash: existingByHash.content_hash ?? hash
74062
74241
  };
74063
74242
  }
@@ -74079,18 +74258,37 @@ class IngestionPipeline {
74079
74258
  embedder: this.embedderModel
74080
74259
  }));
74081
74260
  }
74082
- const rpcResult = await this.db.ingestDocumentRpc({
74083
- documentId: null,
74084
- title,
74085
- source: source ?? sourceOnCreate ?? null,
74086
- sourcePath,
74087
- contentHash: hash,
74088
- metadata: validatedMeta,
74089
- chunks: chunkRows,
74090
- contentFormat: CONTENT_FORMAT_BLIND_STITCH,
74091
- author,
74092
- authorType
74093
- });
74261
+ let rpcResult;
74262
+ try {
74263
+ rpcResult = await this.db.ingestDocumentRpc({
74264
+ documentId: null,
74265
+ title,
74266
+ source: source ?? sourceOnCreate ?? null,
74267
+ sourcePath,
74268
+ contentHash: hash,
74269
+ metadata: validatedMeta,
74270
+ chunks: chunkRows,
74271
+ contentFormat: CONTENT_FORMAT_BLIND_STITCH,
74272
+ author,
74273
+ authorType
74274
+ });
74275
+ } catch (err) {
74276
+ if (!(err instanceof DuplicateContentError))
74277
+ throw err;
74278
+ const existingId = err.existingDocumentId;
74279
+ const existingRow = await this.db.getDocumentById(existingId);
74280
+ return {
74281
+ documentId: existingId,
74282
+ title: existingRow?.title ?? title,
74283
+ chunkCount: existingRow?.chunk_count ?? 0,
74284
+ totalChars: existingRow?.total_chars ?? 0,
74285
+ action: "skipped",
74286
+ reindexed: false,
74287
+ projectIds: await this.db.getDocumentProjectIds(existingId),
74288
+ note: "",
74289
+ contentHash: hash
74290
+ };
74291
+ }
74094
74292
  const newDocumentId = rpcResult.document_id;
74095
74293
  if (resolvedIds.length > 0) {
74096
74294
  await this.db.assignDocumentProjects(newDocumentId, resolvedIds);
@@ -74104,7 +74302,8 @@ class IngestionPipeline {
74104
74302
  reindexed: false,
74105
74303
  projectIds: resolvedIds,
74106
74304
  note: "",
74107
- contentHash: hash
74305
+ contentHash: hash,
74306
+ metadataRevision: rpcResult.metadata_revision
74108
74307
  };
74109
74308
  }
74110
74309
  async updateDocument(opts) {
@@ -74121,7 +74320,9 @@ class IngestionPipeline {
74121
74320
  authorType = "user",
74122
74321
  expectedContentHash,
74123
74322
  lastWriteWins = false,
74124
- forceRechunk = false
74323
+ forceRechunk = false,
74324
+ expectedMetadataRevision = null,
74325
+ replaceMetadata = false
74125
74326
  } = opts;
74126
74327
  const existing = await this.db.getDocumentById(documentId);
74127
74328
  if (!existing) {
@@ -74137,10 +74338,6 @@ class IngestionPipeline {
74137
74338
  if (!lastWriteWins && expectedHashTrimmed && expectedHashTrimmed !== existing.content_hash) {
74138
74339
  throw new ConcurrencyConflictError(documentId, existing.content_hash, `CEREFOX_CONFLICT: document ${documentId} changed since it was read ` + `(expected hash ${expectedHashTrimmed}, current hash ${existing.content_hash}). ` + `Re-read the document, merge your changes, and retry with the new hash.`);
74139
74340
  }
74140
- const collision = await this.db.getDocumentByHash(newHash);
74141
- if (collision && collision.id !== documentId) {
74142
- throw new Error(`Identical content already exists as document ${JSON.stringify(collision.title)}. ` + "Edit that document or change the content before saving.");
74143
- }
74144
74341
  }
74145
74342
  let newProjectIds = null;
74146
74343
  if (projectIds !== undefined && projectIds !== null) {
@@ -74153,11 +74350,21 @@ class IngestionPipeline {
74153
74350
  if (contentUnchanged && hasChunks && !forceRechunk) {
74154
74351
  const oldTitle = existing.title ?? "";
74155
74352
  const titleChanged = oldTitle !== title;
74156
- const updates = { title };
74157
- if (metadata !== undefined && metadata !== null) {
74158
- updates.metadata = metadata;
74353
+ const metadataGiven = metadata !== undefined && metadata !== null;
74354
+ let metadataRevision;
74355
+ if (metadataGiven) {
74356
+ const r = await this.db.setDocumentMetadataRpc({
74357
+ documentId,
74358
+ metadata,
74359
+ replace: replaceMetadata,
74360
+ author,
74361
+ authorType,
74362
+ expectedMetadataRevision,
74363
+ lastWriteWins
74364
+ });
74365
+ metadataRevision = r.metadata_revision;
74159
74366
  }
74160
- await this.db.updateDocumentRow(documentId, updates);
74367
+ await this.db.updateDocumentRow(documentId, { title });
74161
74368
  if (titleChanged) {
74162
74369
  const texts = actualChunks.map((c) => embeddingInputFor(title, c));
74163
74370
  try {
@@ -74183,15 +74390,17 @@ class IngestionPipeline {
74183
74390
  }
74184
74391
  const chunkCount = existing.chunk_count ?? 0;
74185
74392
  const totalChars = existing.total_chars ?? 0;
74186
- await this.db.createAuditEntry({
74187
- operation: "update-metadata",
74188
- author,
74189
- authorType,
74190
- documentId,
74191
- sizeBefore: totalChars,
74192
- sizeAfter: totalChars,
74193
- description: `Updated metadata for '${title}' (content unchanged)`
74194
- });
74393
+ if (!metadataGiven || titleChanged) {
74394
+ await this.db.createAuditEntry({
74395
+ operation: "update-metadata",
74396
+ author,
74397
+ authorType,
74398
+ documentId,
74399
+ sizeBefore: totalChars,
74400
+ sizeAfter: totalChars,
74401
+ description: titleChanged ? `Title changed on '${title}' (was '${oldTitle}'; content unchanged)` : `Updated metadata for '${title}' (content unchanged)`
74402
+ });
74403
+ }
74195
74404
  return {
74196
74405
  documentId,
74197
74406
  title,
@@ -74201,7 +74410,8 @@ class IngestionPipeline {
74201
74410
  reindexed: false,
74202
74411
  projectIds: finalProjectIds,
74203
74412
  note: "",
74204
- contentHash: existing.content_hash ?? newHash
74413
+ contentHash: existing.content_hash ?? newHash,
74414
+ metadataRevision: metadataRevision ?? existing.metadata_revision ?? undefined
74205
74415
  };
74206
74416
  }
74207
74417
  const chunks = chunkMarkdown(text, this.settings.maxChunkChars, this.settings.minChunkChars);
@@ -74221,8 +74431,8 @@ class IngestionPipeline {
74221
74431
  embedder: this.embedderModel
74222
74432
  }));
74223
74433
  }
74224
- const metaToWrite = metadata !== undefined && metadata !== null ? metadata : existing.metadata ?? {};
74225
- await this.db.ingestDocumentRpc({
74434
+ const metaToWrite = metadata ?? null;
74435
+ const rpcResult = await this.db.ingestDocumentRpc({
74226
74436
  documentId,
74227
74437
  title,
74228
74438
  source,
@@ -74235,7 +74445,9 @@ class IngestionPipeline {
74235
74445
  authorType,
74236
74446
  sourceLabel: sourceLabel ?? source ?? "manual",
74237
74447
  expectedContentHash: expectedHashTrimmed ?? (forceRechunk && contentUnchanged ? existing.content_hash : null),
74238
- lastWriteWins
74448
+ lastWriteWins,
74449
+ expectedMetadataRevision,
74450
+ replaceMetadata
74239
74451
  });
74240
74452
  let finalProjectIds;
74241
74453
  if (newProjectIds !== null) {
@@ -74253,7 +74465,8 @@ class IngestionPipeline {
74253
74465
  reindexed: true,
74254
74466
  projectIds: finalProjectIds,
74255
74467
  note: "",
74256
- contentHash: newHash
74468
+ contentHash: newHash,
74469
+ metadataRevision: rpcResult.metadata_revision
74257
74470
  };
74258
74471
  }
74259
74472
  async ingestFile(path, opts = {}) {
@@ -74300,6 +74513,15 @@ async function readContent(path, paste) {
74300
74513
  const titleFromPath = basename2(path, extname3(path));
74301
74514
  return { content, titleFromPath };
74302
74515
  }
74516
+ function parseRevision(raw) {
74517
+ if (raw === undefined)
74518
+ return null;
74519
+ const n = Number(raw);
74520
+ if (!Number.isInteger(n)) {
74521
+ throw userError("--expected-metadata-revision must be an integer (the metadata_revision you read).");
74522
+ }
74523
+ return n;
74524
+ }
74303
74525
  async function action20(path, options) {
74304
74526
  const { content, titleFromPath } = await readContent(path, Boolean(options.paste));
74305
74527
  const updatingById = Boolean(options.documentId);
@@ -74355,6 +74577,7 @@ async function action20(path, options) {
74355
74577
  author,
74356
74578
  authorType,
74357
74579
  expectedContentHash: options.expectedContentHash ?? null,
74580
+ expectedMetadataRevision: parseRevision(options.expectedMetadataRevision),
74358
74581
  lastWriteWins: Boolean(options.lastWriteWins)
74359
74582
  }) : await pipeline.ingestText({
74360
74583
  text: content,
@@ -74369,6 +74592,7 @@ async function action20(path, options) {
74369
74592
  author,
74370
74593
  authorType,
74371
74594
  expectedContentHash: options.expectedContentHash ?? null,
74595
+ expectedMetadataRevision: parseRevision(options.expectedMetadataRevision),
74372
74596
  lastWriteWins: Boolean(options.lastWriteWins)
74373
74597
  });
74374
74598
  let verb;
@@ -74394,7 +74618,7 @@ async function action20(path, options) {
74394
74618
  }
74395
74619
  }
74396
74620
  function registerIngest(program) {
74397
- program.command("ingest").description("Ingest a file (or stdin paste) into the knowledge base.").argument("[path]", "Path to the file to ingest. Omit when using --paste.").option("--paste", "Read content from stdin instead of a file.").option("-t, --title <title>", "Document title (required with --paste; defaults to filename without extension).").option("-p, --project-name <name>", "Single project membership (non-destructive on update).").option("-P, --project-names <names>", "Comma-separated full project membership set (destructive replace on update).").option("-m, --metadata <json>", "JSON metadata object.").option("--source <label>", 'Origin label. Omit it on an update and the document keeps the source it already has (#193); omit it on a create and it is recorded as "cli".').option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-i, --document-id <uuid>", "Update a specific document by UUID (overrides --update-if-exists).").option("--expected-content-hash <sha256>", "Optimistic-concurrency token: the content_hash of the version this edit is based on (shown by `document get` / `search`). Required on content updates unless --last-write-wins.").option("--last-write-wins", "Skip the concurrency check and overwrite regardless of concurrent changes (recorded in the audit log).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action20);
74621
+ program.command("ingest").description("Ingest a file (or stdin paste) into the knowledge base.").argument("[path]", "Path to the file to ingest. Omit when using --paste.").option("--paste", "Read content from stdin instead of a file.").option("-t, --title <title>", "Document title (required with --paste; defaults to filename without extension).").option("-p, --project-name <name>", "Single project membership (non-destructive on update).").option("-P, --project-names <names>", "Comma-separated full project membership set (destructive replace on update).").option("-m, --metadata <json>", "JSON metadata object.").option("--source <label>", 'Origin label. Omit it on an update and the document keeps the source it already has (#193); omit it on a create and it is recorded as "cli".').option("-u, --update-if-exists", "Update an existing doc with the same title.").option("-i, --document-id <uuid>", "Update a specific document by UUID (overrides --update-if-exists).").option("--expected-metadata-revision <n>", "With --metadata on an update: the metadata_revision you read. Fails if the metadata changed since (metadata merges; a null value removes a key).").option("--expected-content-hash <sha256>", "Optimistic-concurrency token: the content_hash of the version this edit is based on (shown by `document get` / `search`). Required on content updates unless --last-write-wins.").option("--last-write-wins", "Skip the concurrency check and overwrite regardless of concurrent changes (recorded in the audit log).").option("-a, --author <name>", "Caller identity (audit log).").option("--author-type <type>", "'user' or 'agent' (default: user).", "user").action(action20);
74398
74622
  }
74399
74623
 
74400
74624
  // src/cli/commands/document-partial-edit.ts
@@ -75497,7 +75721,6 @@ async function action30(options) {
75497
75721
  });
75498
75722
  let converted = 0;
75499
75723
  let skipped = 0;
75500
- const duplicates = [];
75501
75724
  const failures = [];
75502
75725
  for (let i = 0;i < targets.length; i++) {
75503
75726
  const id = targets[i];
@@ -75540,8 +75763,6 @@ async function action30(options) {
75540
75763
  const message = err instanceof Error ? err.message : String(err);
75541
75764
  if (/conflict/i.test(message)) {
75542
75765
  skipped++;
75543
- } else if (/identical content already exists/i.test(message)) {
75544
- duplicates.push({ document: `${doc.doc_title} (${id})`, reason: message });
75545
75766
  } else {
75546
75767
  failures.push({ document: `${doc.doc_title} (${id})`, reason: message });
75547
75768
  }
@@ -75555,12 +75776,6 @@ async function action30(options) {
75555
75776
  if (targets.length < docIds.length) {
75556
75777
  println(c.dim(` ${docIds.length - targets.length} document(s) still pending — re-run to continue.`));
75557
75778
  }
75558
- if (duplicates.length > 0) {
75559
- println("");
75560
- println(c.yellow(`⚠ ${duplicates.length} document(s) could not be converted because their content is ` + "byte-identical to another document."));
75561
- println(c.dim(" Re-ingesting them would collide with the content-hash dedup check. They keep working " + "on the legacy format; de-duplicate them if you want them converted."));
75562
- printTable(duplicates.map((d) => ({ document: d.document })));
75563
- }
75564
75779
  if (failures.length > 0) {
75565
75780
  println("");
75566
75781
  printTable(failures);
@@ -75634,8 +75849,20 @@ async function action31(target, options) {
75634
75849
  let skipped = 0;
75635
75850
  let errors = 0;
75636
75851
  const errorDetails = [];
75852
+ const payloadIds = new Set(payload.documents.map((d) => d.id));
75637
75853
  for (const doc of payload.documents) {
75638
- const { data: existing } = await client.raw.from("cerefox_documents").select("id").or(`id.eq.${doc.id},content_hash.eq.${doc.content_hash}`).limit(1).maybeSingle();
75854
+ const { data: byId } = await client.raw.from("cerefox_documents").select("id").eq("id", doc.id).limit(1);
75855
+ let existing = (byId ?? []).length > 0;
75856
+ if (!existing && !doc.deleted_at && doc.content_hash) {
75857
+ const PAGE = 500;
75858
+ for (let from = 0;!existing; from += PAGE) {
75859
+ const { data: byHash } = await client.raw.from("cerefox_documents").select("id").eq("content_hash", doc.content_hash).is("deleted_at", null).order("id").range(from, from + PAGE - 1);
75860
+ const rows = byHash ?? [];
75861
+ existing = rows.some((m) => !payloadIds.has(m.id));
75862
+ if (rows.length < PAGE)
75863
+ break;
75864
+ }
75865
+ }
75639
75866
  if (existing) {
75640
75867
  skipped++;
75641
75868
  continue;
@@ -75893,7 +76120,7 @@ async function action32(query, options) {
75893
76120
  println(c.bold(`## ${title}${docId}${score}${counts}${kind}`));
75894
76121
  const bestMatch = doc.best_chunk_heading_path?.length ? doc.best_chunk_heading_path.join(" › ") : null;
75895
76122
  const updated = doc.doc_updated_at ? doc.doc_updated_at.slice(0, 10) : null;
75896
- const hash = doc.content_hash ? `hash: ${doc.content_hash}` : null;
76123
+ const hash = doc.content_hash ? `hash: ${doc.content_hash}` + (doc.metadata_revision != null ? ` · metadata_revision: ${doc.metadata_revision}` : "") : null;
75897
76124
  if (bestMatch || updated || hash) {
75898
76125
  const bits = [
75899
76126
  bestMatch ? `best match: ${bestMatch}` : null,
@@ -76347,41 +76574,38 @@ init_sync_self_docs();
76347
76574
  init_cli_core();
76348
76575
  import { readFileSync as readFileSync21 } from "node:fs";
76349
76576
 
76350
- // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
76577
+ // ../../node_modules/.bun/@hono+node-server@2.1.4+c539cad7f494f5c9/node_modules/@hono/node-server/dist/constants-BLSFu_RU.mjs
76351
76578
  var X_ALREADY_SENT = "x-hono-already-sent";
76352
76579
 
76353
- // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/index.mjs
76354
- import { STATUS_CODES, createServer } from "node:http";
76580
+ // ../../node_modules/.bun/@hono+node-server@2.1.4+c539cad7f494f5c9/node_modules/@hono/node-server/dist/index.mjs
76581
+ import { STATUS_CODES, ServerResponse, createServer } from "node:http";
76355
76582
  import { Http2ServerRequest, constants } from "node:http2";
76356
76583
  import { Readable } from "node:stream";
76357
76584
 
76358
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/helper/websocket/index.js
76585
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/helper/websocket/index.js
76359
76586
  var defineWebSocketHelper = (handler) => {
76360
76587
  return (...args) => {
76361
76588
  if (typeof args[0] === "function") {
76362
76589
  const [createEvents, options] = args;
76363
76590
  return async function upgradeWebSocket(c, next) {
76364
- const events = await createEvents(c);
76365
- const result = await handler(c, events, options);
76366
- if (result) {
76591
+ const result = await handler(c, await createEvents(c), options);
76592
+ if (result)
76367
76593
  return result;
76368
- }
76369
76594
  await next();
76370
76595
  };
76371
76596
  } else {
76372
76597
  const [c, events, options] = args;
76373
76598
  return (async () => {
76374
76599
  const upgraded = await handler(c, events, options);
76375
- if (!upgraded) {
76600
+ if (!upgraded)
76376
76601
  throw new Error("Failed to upgrade WebSocket");
76377
- }
76378
76602
  return upgraded;
76379
76603
  })();
76380
76604
  }
76381
76605
  };
76382
76606
  };
76383
76607
 
76384
- // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/index.mjs
76608
+ // ../../node_modules/.bun/@hono+node-server@2.1.4+c539cad7f494f5c9/node_modules/@hono/node-server/dist/index.mjs
76385
76609
  var RequestError = class extends Error {
76386
76610
  constructor(message, options) {
76387
76611
  super(message, options);
@@ -77337,8 +77561,11 @@ var handleResponseError = (e, outgoing) => {
77337
77561
  console.info("The user aborted a request.");
77338
77562
  else {
77339
77563
  console.error(e);
77340
- if (!outgoing.headersSent)
77564
+ if (!outgoing.headersSent) {
77565
+ if (outgoing instanceof ServerResponse)
77566
+ outgoing._contentLength = null;
77341
77567
  outgoing.writeHead(500, { "Content-Type": "text/plain" });
77568
+ }
77342
77569
  outgoing.end(`Error: ${err.message}`);
77343
77570
  outgoing.destroy(err);
77344
77571
  }
@@ -77347,6 +77574,23 @@ var flushHeaders = (outgoing) => {
77347
77574
  if ("flushHeaders" in outgoing && outgoing.writable)
77348
77575
  outgoing.flushHeaders();
77349
77576
  };
77577
+ var trySetContentLength = (outgoing, status, length) => {
77578
+ const http1 = outgoing;
77579
+ if (http1._contentLength === null && http1._hasBody && http1.useChunkedEncodingByDefault && !http1._removedContLen && status >= 200 && status !== 204 && status !== 304 && !outgoing.hasHeader("content-length") && !outgoing.hasHeader("transfer-encoding") && !outgoing.hasHeader("trailer")) {
77580
+ http1._contentLength = length;
77581
+ return true;
77582
+ }
77583
+ return false;
77584
+ };
77585
+ var writeDefaultHeaders = (outgoing, status, length) => {
77586
+ if (trySetContentLength(outgoing, status, length))
77587
+ outgoing.writeHead(status, { "Content-Type": defaultContentType });
77588
+ else
77589
+ outgoing.writeHead(status, {
77590
+ "Content-Type": defaultContentType,
77591
+ "Content-Length": length
77592
+ });
77593
+ };
77350
77594
  var responseViaCache = async (res, outgoing) => {
77351
77595
  let [status, body, header] = res[cacheKey];
77352
77596
  if (!header) {
@@ -77354,22 +77598,13 @@ var responseViaCache = async (res, outgoing) => {
77354
77598
  outgoing.writeHead(status);
77355
77599
  outgoing.end();
77356
77600
  } else if (typeof body === "string") {
77357
- outgoing.writeHead(status, {
77358
- "Content-Type": defaultContentType,
77359
- "Content-Length": Buffer.byteLength(body)
77360
- });
77601
+ writeDefaultHeaders(outgoing, status, Buffer.byteLength(body));
77361
77602
  outgoing.end(body);
77362
77603
  } else if (body instanceof Uint8Array) {
77363
- outgoing.writeHead(status, {
77364
- "Content-Type": defaultContentType,
77365
- "Content-Length": body.byteLength
77366
- });
77604
+ writeDefaultHeaders(outgoing, status, body.byteLength);
77367
77605
  outgoing.end(body);
77368
77606
  } else if (body instanceof Blob) {
77369
- outgoing.writeHead(status, {
77370
- "Content-Type": defaultContentType,
77371
- "Content-Length": body.size
77372
- });
77607
+ writeDefaultHeaders(outgoing, status, body.size);
77373
77608
  outgoing.end(new Uint8Array(await body.arrayBuffer()));
77374
77609
  } else {
77375
77610
  outgoing.writeHead(status, { "Content-Type": defaultContentType });
@@ -77380,6 +77615,8 @@ var responseViaCache = async (res, outgoing) => {
77380
77615
  return;
77381
77616
  }
77382
77617
  let hasContentLength = false;
77618
+ let plainHeaders = false;
77619
+ let canAutoLength = true;
77383
77620
  if (header instanceof Headers) {
77384
77621
  hasContentLength = header.has("content-length");
77385
77622
  header = buildOutgoingHttpHeaders(header, body === null ? undefined : defaultContentType);
@@ -77387,19 +77624,30 @@ var responseViaCache = async (res, outgoing) => {
77387
77624
  const headerObj = new Headers(header);
77388
77625
  hasContentLength = headerObj.has("content-length");
77389
77626
  header = buildOutgoingHttpHeaders(headerObj, body === null ? undefined : defaultContentType);
77390
- } else
77391
- for (const key in header)
77627
+ } else {
77628
+ plainHeaders = true;
77629
+ for (const key in header) {
77392
77630
  if (key.length === 14 && key.toLowerCase() === "content-length") {
77393
77631
  hasContentLength = true;
77394
77632
  break;
77395
77633
  }
77634
+ if (key.length === 17 && key.toLowerCase() === "transfer-encoding" || key.length === 7 && key.toLowerCase() === "trailer")
77635
+ canAutoLength = false;
77636
+ }
77637
+ }
77396
77638
  if (!hasContentLength) {
77639
+ let length;
77397
77640
  if (typeof body === "string")
77398
- header["Content-Length"] = Buffer.byteLength(body);
77641
+ length = Buffer.byteLength(body);
77399
77642
  else if (body instanceof Uint8Array)
77400
- header["Content-Length"] = body.byteLength;
77643
+ length = body.byteLength;
77401
77644
  else if (body instanceof Blob)
77402
- header["Content-Length"] = body.size;
77645
+ length = body.size;
77646
+ if (length !== undefined && (!plainHeaders || !canAutoLength || !trySetContentLength(outgoing, status, length))) {
77647
+ if (plainHeaders)
77648
+ header = { ...header };
77649
+ header["Content-Length"] = length;
77650
+ }
77403
77651
  }
77404
77652
  outgoing.writeHead(status, header);
77405
77653
  if (body == null)
@@ -77810,7 +78058,7 @@ var serve = (options, listeningListener) => {
77810
78058
  return server;
77811
78059
  };
77812
78060
 
77813
- // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/utils/stream.mjs
78061
+ // ../../node_modules/.bun/@hono+node-server@2.1.4+c539cad7f494f5c9/node_modules/@hono/node-server/dist/utils/stream.mjs
77814
78062
  import { Readable as Readable2 } from "node:stream";
77815
78063
  import { versions as versions2 } from "node:process";
77816
78064
  var pr54206Applied = () => {
@@ -77876,16 +78124,14 @@ var createStreamBody = (stream, useNativeReadableToWeb = useReadableToWeb) => {
77876
78124
  });
77877
78125
  };
77878
78126
 
77879
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/mime.js
78127
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/mime.js
77880
78128
  var getMimeType = (filename, mimes = baseMimes) => {
77881
- const regexp = /\.([a-zA-Z0-9]+?)$/;
77882
- const match = filename.match(regexp);
77883
- if (!match) {
78129
+ const match = filename.match(/\.([a-zA-Z0-9]+?)$/);
78130
+ if (!match)
77884
78131
  return;
77885
- }
77886
78132
  return mimes[match[1].toLowerCase()];
77887
78133
  };
77888
- var _baseMimes = {
78134
+ var baseMimes = {
77889
78135
  aac: "audio/aac",
77890
78136
  avi: "video/x-msvideo",
77891
78137
  avif: "image/avif",
@@ -77943,9 +78189,8 @@ var _baseMimes = {
77943
78189
  gltf: "model/gltf+json",
77944
78190
  glb: "model/gltf-binary"
77945
78191
  };
77946
- var baseMimes = _baseMimes;
77947
78192
 
77948
- // ../../node_modules/.bun/@hono+node-server@2.1.1+2ac783cc5e75a70c/node_modules/@hono/node-server/dist/serve-static.mjs
78193
+ // ../../node_modules/.bun/@hono+node-server@2.1.4+c539cad7f494f5c9/node_modules/@hono/node-server/dist/serve-static.mjs
77949
78194
  import { createReadStream, existsSync as existsSync16, statSync as statSync6 } from "node:fs";
77950
78195
  import { join as join15 } from "node:path";
77951
78196
  var COMPRESSIBLE_CONTENT_TYPE_REGEX = /^\s*(?:text\/[^;\s]+|application\/(?:javascript|json|xml|xml-dtd|ecmascript|dart|postscript|rtf|tar|toml|vnd\.dart|vnd\.ms-fontobject|vnd\.ms-opentype|wasm|x-httpd-php|x-javascript|x-ns-proxy-autoconfig|x-sh|x-tar|x-virtualbox-hdd|x-virtualbox-ova|x-virtualbox-ovf|x-virtualbox-vbox|x-virtualbox-vdi|x-virtualbox-vhd|x-virtualbox-vmdk|x-www-form-urlencoded)|font\/(?:otf|ttf)|image\/(?:bmp|vnd\.adobe\.photoshop|vnd\.microsoft\.icon|vnd\.ms-dds|x-icon|x-ms-bmp)|message\/rfc822|model\/gltf-binary|x-shader\/x-fragment|x-shader\/x-vertex|[^;\s]+?\+(?:json|text|xml|yaml))(?:[;\s]|$)/i;
@@ -78030,6 +78275,8 @@ var serveStatic = (options = { root: "" }) => {
78030
78275
  filename = optionPath;
78031
78276
  else
78032
78277
  try {
78278
+ if (!options.allowPercentInPath && c.req.path.includes("%"))
78279
+ throw new Error;
78033
78280
  filename = tryDecodeURI(c.req.path);
78034
78281
  if (/(?:^|[\/\\])\.{1,2}(?:$|[\/\\])|[\/\\]{2,}|\\/.test(filename))
78035
78282
  throw new Error;
@@ -78108,150 +78355,86 @@ import { existsSync as existsSync20 } from "node:fs";
78108
78355
  import { readFileSync as readFileSync20, statSync as statSync9 } from "node:fs";
78109
78356
  import { join as join19 } from "node:path";
78110
78357
 
78111
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/compose.js
78112
- var compose = (middleware, onError, onNotFound) => {
78113
- return (context, next) => {
78114
- let index = -1;
78115
- return dispatch(0);
78116
- async function dispatch(i) {
78117
- if (i <= index) {
78118
- throw new Error("next() called multiple times");
78119
- }
78120
- index = i;
78121
- let res;
78122
- let isError = false;
78123
- let handler;
78124
- if (middleware[i]) {
78125
- handler = middleware[i][0][0];
78126
- context.req.routeIndex = i;
78127
- } else {
78128
- handler = i === middleware.length && next || undefined;
78129
- }
78130
- if (handler) {
78131
- try {
78132
- res = await handler(context, () => dispatch(i + 1));
78133
- } catch (err) {
78134
- if (err instanceof Error && onError) {
78135
- context.error = err;
78136
- res = await onError(err, context);
78137
- isError = true;
78138
- } else {
78139
- throw err;
78140
- }
78141
- }
78142
- } else {
78143
- if (context.finalized === false && onNotFound) {
78144
- res = await onNotFound(context);
78145
- }
78146
- }
78147
- if (res && (context.finalized === false || isError)) {
78148
- context.res = res;
78149
- }
78150
- return context;
78151
- }
78152
- };
78153
- };
78154
-
78155
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/request/constants.js
78156
- var GET_MATCH_RESULT = /* @__PURE__ */ Symbol();
78358
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/request/constants.js
78359
+ var GET_MATCH_RESULT = Symbol();
78157
78360
 
78158
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/buffer.js
78361
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/buffer.js
78159
78362
  var bufferToFormData = (arrayBuffer, contentType) => {
78160
- const response = new Response(arrayBuffer, {
78161
- headers: {
78162
- "Content-Type": contentType.replace(/^[^;]+/, (mediaType) => mediaType.toLowerCase())
78163
- }
78164
- });
78165
- return response.formData();
78363
+ return new Response(arrayBuffer, { headers: { "Content-Type": contentType.replace(/^[^;]+/, (mediaType) => mediaType.toLowerCase()) } }).formData();
78166
78364
  };
78167
78365
 
78168
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/body.js
78169
- var MAX_NESTING_DEPTH = 32;
78366
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/body.js
78170
78367
  var MAX_NESTED_OBJECTS = 1e4;
78171
78368
  var isRawRequest = (request) => ("headers" in request);
78172
- var parseBody = async (request, options = /* @__PURE__ */ Object.create(null)) => {
78369
+ var parseBody = async (request, options = Object.create(null)) => {
78173
78370
  const { all = false, dot = false } = options;
78174
- const headers = isRawRequest(request) ? request.headers : request.raw.headers;
78175
- const contentType = headers.get("Content-Type");
78176
- const mediaType = contentType?.split(";")[0].trim().toLowerCase();
78177
- if (mediaType === "multipart/form-data" || mediaType === "application/x-www-form-urlencoded") {
78178
- return parseFormData(request, { all, dot });
78179
- }
78371
+ const mediaType = (isRawRequest(request) ? request.headers : request.raw.headers).get("Content-Type")?.split(";")[0].trim().toLowerCase();
78372
+ if (mediaType === "multipart/form-data" || mediaType === "application/x-www-form-urlencoded")
78373
+ return parseFormData(request, {
78374
+ all,
78375
+ dot
78376
+ });
78180
78377
  return {};
78181
78378
  };
78182
78379
  async function parseFormData(request, options) {
78183
- if (!isRawRequest(request) && request.bodyCache.formData) {
78380
+ if (!isRawRequest(request) && request.bodyCache.formData)
78184
78381
  return convertFormDataToBodyData(await request.bodyCache.formData, options);
78185
- }
78186
78382
  const headers = isRawRequest(request) ? request.headers : request.raw.headers;
78187
78383
  const arrayBuffer = await request.arrayBuffer();
78188
78384
  const formDataPromise = bufferToFormData(arrayBuffer, headers.get("Content-Type") || "");
78189
- if (!isRawRequest(request)) {
78385
+ if (!isRawRequest(request))
78190
78386
  request.bodyCache.formData = formDataPromise;
78191
- }
78192
78387
  const formData = await formDataPromise;
78193
- if (formData) {
78388
+ if (formData)
78194
78389
  return convertFormDataToBodyData(formData, options);
78195
- }
78196
78390
  return {};
78197
78391
  }
78198
78392
  function convertFormDataToBodyData(formData, options) {
78199
- const form = /* @__PURE__ */ Object.create(null);
78393
+ const form = Object.create(null);
78200
78394
  const nestingState = { count: 0 };
78201
78395
  formData.forEach((value, key) => {
78202
- const shouldParseAllValues = options.all || key.endsWith("[]");
78203
- if (!shouldParseAllValues) {
78396
+ if (!(options.all || key.endsWith("[]")))
78204
78397
  form[key] = value;
78205
- } else {
78398
+ else
78206
78399
  handleParsingAllValues(form, key, value);
78207
- }
78208
78400
  });
78209
- if (options.dot) {
78401
+ if (options.dot)
78210
78402
  Object.entries(form).forEach(([key, value]) => {
78211
- const shouldParseDotValues = key.includes(".");
78212
- if (shouldParseDotValues) {
78403
+ if (key.includes(".")) {
78213
78404
  handleParsingNestedValues(form, key, value, nestingState);
78214
78405
  delete form[key];
78215
78406
  }
78216
78407
  });
78217
- }
78218
78408
  return form;
78219
78409
  }
78220
78410
  var handleParsingAllValues = (form, key, value) => {
78221
78411
  if (form[key] !== undefined) {
78222
- if (Array.isArray(form[key])) {
78412
+ if (Array.isArray(form[key]))
78223
78413
  form[key].push(value);
78224
- } else {
78414
+ else
78225
78415
  form[key] = [form[key], value];
78226
- }
78227
- } else {
78228
- if (!key.endsWith("[]")) {
78229
- form[key] = value;
78230
- } else {
78231
- form[key] = [value];
78232
- }
78233
- }
78416
+ } else if (!key.endsWith("[]"))
78417
+ form[key] = value;
78418
+ else
78419
+ form[key] = [value];
78234
78420
  };
78235
78421
  var handleParsingNestedValues = (form, key, value, state) => {
78236
- if (/(?:^|\.)__proto__\./.test(key)) {
78422
+ if (/(?:^|\.)__proto__\./.test(key))
78237
78423
  return;
78238
- }
78239
78424
  let nestedForm = form;
78240
- const keys = key.split(".", MAX_NESTING_DEPTH + 2);
78241
- if (keys.length > MAX_NESTING_DEPTH + 1) {
78425
+ const keys = key.split(".", 34);
78426
+ if (keys.length > 33)
78242
78427
  throwNestingLimitExceeded();
78243
- }
78244
- keys.forEach((key2, index) => {
78245
- if (index === keys.length - 1) {
78246
- nestedForm[key2] = value;
78247
- } else {
78248
- if (!nestedForm[key2] || typeof nestedForm[key2] !== "object" || Array.isArray(nestedForm[key2]) || nestedForm[key2] instanceof File) {
78249
- if (state.count++ >= MAX_NESTED_OBJECTS) {
78428
+ keys.forEach((key, index) => {
78429
+ if (index === keys.length - 1)
78430
+ nestedForm[key] = value;
78431
+ else {
78432
+ if (!nestedForm[key] || typeof nestedForm[key] !== "object" || Array.isArray(nestedForm[key]) || nestedForm[key] instanceof File) {
78433
+ if (state.count++ >= MAX_NESTED_OBJECTS)
78250
78434
  throwNestingLimitExceeded();
78251
- }
78252
- nestedForm[key2] = /* @__PURE__ */ Object.create(null);
78435
+ nestedForm[key] = Object.create(null);
78253
78436
  }
78254
- nestedForm = nestedForm[key2];
78437
+ nestedForm = nestedForm[key];
78255
78438
  }
78256
78439
  });
78257
78440
  };
@@ -78259,12 +78442,11 @@ var throwNestingLimitExceeded = () => {
78259
78442
  throw new Error("Nesting limit exceeded");
78260
78443
  };
78261
78444
 
78262
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/url.js
78445
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/url.js
78263
78446
  var splitPath = (path) => {
78264
78447
  const paths = path.split("/");
78265
- if (paths[0] === "") {
78448
+ if (paths[0] === "")
78266
78449
  paths.shift();
78267
- }
78268
78450
  return paths;
78269
78451
  };
78270
78452
  var splitRoutingPath = (routePath) => {
@@ -78279,34 +78461,46 @@ var extractGroupsFromPath = (path) => {
78279
78461
  groups.push([mark, match]);
78280
78462
  return mark;
78281
78463
  });
78282
- return { groups, path };
78464
+ return {
78465
+ groups,
78466
+ path
78467
+ };
78283
78468
  };
78284
78469
  var replaceGroupMarks = (paths, groups) => {
78285
78470
  for (let i = groups.length - 1;i >= 0; i--) {
78286
78471
  const [mark] = groups[i];
78287
- for (let j = paths.length - 1;j >= 0; j--) {
78472
+ for (let j = paths.length - 1;j >= 0; j--)
78288
78473
  if (paths[j].includes(mark)) {
78289
78474
  paths[j] = paths[j].replace(mark, groups[i][1]);
78290
78475
  break;
78291
78476
  }
78292
- }
78293
78477
  }
78294
78478
  return paths;
78295
78479
  };
78296
78480
  var patternCache = {};
78297
78481
  var getPattern = (label, next) => {
78298
- if (label === "*") {
78482
+ if (label === "*")
78299
78483
  return "*";
78300
- }
78301
78484
  const match = label.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
78302
78485
  if (match) {
78303
78486
  const cacheKey = `${label}#${next}`;
78304
78487
  if (!patternCache[cacheKey]) {
78305
- if (match[2]) {
78306
- patternCache[cacheKey] = next && next[0] !== ":" && next[0] !== "*" ? [cacheKey, match[1], new RegExp(`^${match[2]}(?=/${next})`)] : [label, match[1], new RegExp(`^${match[2]}$`)];
78307
- } else {
78308
- patternCache[cacheKey] = [label, match[1], true];
78309
- }
78488
+ if (match[2])
78489
+ patternCache[cacheKey] = next && next[0] !== ":" && next[0] !== "*" ? [
78490
+ cacheKey,
78491
+ match[1],
78492
+ new RegExp(`^${match[2]}(?=/${next})`)
78493
+ ] : [
78494
+ label,
78495
+ match[1],
78496
+ new RegExp(`^${match[2]}$`)
78497
+ ];
78498
+ else
78499
+ patternCache[cacheKey] = [
78500
+ label,
78501
+ match[1],
78502
+ true
78503
+ ];
78310
78504
  }
78311
78505
  return patternCache[cacheKey];
78312
78506
  }
@@ -78338,9 +78532,8 @@ var getPath = (request) => {
78338
78532
  const end = queryIndex === -1 ? hashIndex === -1 ? undefined : hashIndex : hashIndex === -1 ? queryIndex : Math.min(queryIndex, hashIndex);
78339
78533
  const path = url.slice(start, end);
78340
78534
  return tryDecodeURI2(path.includes("%25") ? path.replace(/%25/g, "%2525") : path);
78341
- } else if (charCode === 63 || charCode === 35) {
78535
+ } else if (charCode === 63 || charCode === 35)
78342
78536
  break;
78343
- }
78344
78537
  }
78345
78538
  return url.slice(start, i);
78346
78539
  };
@@ -78349,109 +78542,93 @@ var getPathNoStrict = (request) => {
78349
78542
  return result.length > 1 && result.at(-1) === "/" ? result.slice(0, -1) : result;
78350
78543
  };
78351
78544
  var mergePath = (base, sub, ...rest) => {
78352
- if (rest.length) {
78545
+ if (rest.length)
78353
78546
  sub = mergePath(sub, ...rest);
78354
- }
78355
78547
  return `${base?.[0] === "/" ? "" : "/"}${base}${sub === "/" ? "" : `${base?.at(-1) === "/" ? "" : "/"}${sub?.[0] === "/" ? sub.slice(1) : sub}`}`;
78356
78548
  };
78357
78549
  var checkOptionalParameter = (path) => {
78358
- if (path.charCodeAt(path.length - 1) !== 63 || !path.includes(":")) {
78550
+ if (path.charCodeAt(path.length - 1) !== 63 || !path.includes(":"))
78359
78551
  return null;
78360
- }
78361
78552
  const segments = path.split("/");
78362
78553
  const results = [];
78363
78554
  let basePath = "";
78364
78555
  segments.forEach((segment) => {
78365
- if (segment !== "" && !/\:/.test(segment)) {
78556
+ if (segment !== "" && !/\:/.test(segment))
78366
78557
  basePath += "/" + segment;
78367
- } else if (/\:/.test(segment)) {
78558
+ else if (/\:/.test(segment)) {
78368
78559
  if (segment.charCodeAt(segment.length - 1) === 63) {
78369
- if (results.length === 0 && basePath === "") {
78560
+ if (results.length === 0 && basePath === "")
78370
78561
  results.push("/");
78371
- } else {
78562
+ else
78372
78563
  results.push(basePath);
78373
- }
78374
78564
  const optionalSegment = segment.slice(0, -1);
78375
78565
  basePath += "/" + optionalSegment;
78376
78566
  results.push(basePath);
78377
- } else {
78567
+ } else
78378
78568
  basePath += "/" + segment;
78379
- }
78380
78569
  }
78381
78570
  });
78382
78571
  return results.filter((v, i, a) => a.indexOf(v) === i);
78383
78572
  };
78384
78573
  var tryDecodeURIComponent = (str) => str.indexOf("%") !== -1 ? tryDecode2(str, decodeURIComponent_) : str;
78385
78574
  var _decodeURI = (value) => {
78386
- if (value.indexOf("+") !== -1) {
78575
+ if (value.indexOf("+") !== -1)
78387
78576
  value = value.replace(/\+/g, " ");
78388
- }
78389
78577
  return tryDecodeURIComponent(value);
78390
78578
  };
78391
78579
  var _getQueryParam = (url, key, multiple) => {
78392
78580
  const hashIndex = url.indexOf("#", 8);
78393
- if (hashIndex !== -1) {
78581
+ if (hashIndex !== -1)
78394
78582
  url = url.slice(0, hashIndex);
78395
- }
78396
78583
  let encoded;
78397
78584
  if (!multiple && key && key.indexOf("%") === -1 && key.indexOf("+") === -1) {
78398
- let keyIndex2 = url.indexOf("?", 8);
78399
- if (keyIndex2 === -1) {
78585
+ let keyIndex = url.indexOf("?", 8);
78586
+ if (keyIndex === -1)
78400
78587
  return;
78401
- }
78402
- if (!url.startsWith(key, keyIndex2 + 1)) {
78403
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
78404
- }
78405
- while (keyIndex2 !== -1) {
78406
- const trailingKeyCode = url.charCodeAt(keyIndex2 + key.length + 1);
78588
+ if (!url.startsWith(key, keyIndex + 1))
78589
+ keyIndex = url.indexOf(`&${key}`, keyIndex + 1);
78590
+ while (keyIndex !== -1) {
78591
+ const trailingKeyCode = url.charCodeAt(keyIndex + key.length + 1);
78407
78592
  if (trailingKeyCode === 61) {
78408
- const valueIndex = keyIndex2 + key.length + 2;
78593
+ const valueIndex = keyIndex + key.length + 2;
78409
78594
  const endIndex = url.indexOf("&", valueIndex);
78410
78595
  return _decodeURI(url.slice(valueIndex, endIndex === -1 ? undefined : endIndex));
78411
- } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode)) {
78596
+ } else if (trailingKeyCode == 38 || isNaN(trailingKeyCode))
78412
78597
  return "";
78413
- }
78414
- keyIndex2 = url.indexOf(`&${key}`, keyIndex2 + 1);
78598
+ keyIndex = url.indexOf(`&${key}`, keyIndex + 1);
78415
78599
  }
78416
78600
  encoded = /[%+]/.test(url);
78417
- if (!encoded) {
78601
+ if (!encoded)
78418
78602
  return;
78419
- }
78420
78603
  }
78421
- const results = /* @__PURE__ */ Object.create(null);
78604
+ const results = Object.create(null);
78422
78605
  encoded ??= /[%+]/.test(url);
78423
78606
  let keyIndex = url.indexOf("?", 8);
78424
78607
  while (keyIndex !== -1) {
78425
78608
  const nextKeyIndex = url.indexOf("&", keyIndex + 1);
78426
78609
  let valueIndex = url.indexOf("=", keyIndex);
78427
- if (valueIndex > nextKeyIndex && nextKeyIndex !== -1) {
78610
+ if (valueIndex > nextKeyIndex && nextKeyIndex !== -1)
78428
78611
  valueIndex = -1;
78429
- }
78430
78612
  let name = url.slice(keyIndex + 1, valueIndex === -1 ? nextKeyIndex === -1 ? undefined : nextKeyIndex : valueIndex);
78431
- if (encoded) {
78613
+ if (encoded)
78432
78614
  name = _decodeURI(name);
78433
- }
78434
78615
  keyIndex = nextKeyIndex;
78435
- if (name === "") {
78616
+ if (name === "")
78436
78617
  continue;
78437
- }
78438
78618
  let value;
78439
- if (valueIndex === -1) {
78619
+ if (valueIndex === -1)
78440
78620
  value = "";
78441
- } else {
78621
+ else {
78442
78622
  value = url.slice(valueIndex + 1, nextKeyIndex === -1 ? undefined : nextKeyIndex);
78443
- if (encoded) {
78623
+ if (encoded)
78444
78624
  value = _decodeURI(value);
78445
- }
78446
78625
  }
78447
78626
  if (multiple) {
78448
- if (!(results[name] && Array.isArray(results[name]))) {
78627
+ if (!(results[name] && Array.isArray(results[name])))
78449
78628
  results[name] = [];
78450
- }
78451
78629
  results[name].push(value);
78452
- } else {
78630
+ } else
78453
78631
  results[name] ??= value;
78454
- }
78455
78632
  }
78456
78633
  return key ? results[key] : results;
78457
78634
  };
@@ -78461,7 +78638,7 @@ var getQueryParams = (url, key) => {
78461
78638
  };
78462
78639
  var decodeURIComponent_ = decodeURIComponent;
78463
78640
 
78464
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/request.js
78641
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/request.js
78465
78642
  var HonoRequest = class {
78466
78643
  raw;
78467
78644
  #validatedData;
@@ -78487,9 +78664,8 @@ var HonoRequest = class {
78487
78664
  const keys = Object.keys(this.#matchResult[0][this.routeIndex]?.[1] ?? {});
78488
78665
  for (const key of keys) {
78489
78666
  const value = this.#getParamValue(this.#matchResult[0][this.routeIndex][1][key]);
78490
- if (value !== undefined) {
78667
+ if (value !== undefined)
78491
78668
  decoded[key] = tryDecodeURIComponent(value);
78492
- }
78493
78669
  }
78494
78670
  return decoded;
78495
78671
  }
@@ -78503,10 +78679,9 @@ var HonoRequest = class {
78503
78679
  return getQueryParams(this.url, key);
78504
78680
  }
78505
78681
  header(name) {
78506
- if (name) {
78682
+ if (name)
78507
78683
  return this.raw.headers.get(name) ?? undefined;
78508
- }
78509
- const headerData = /* @__PURE__ */ Object.create(null);
78684
+ const headerData = Object.create(null);
78510
78685
  this.raw.headers.forEach((value, key) => {
78511
78686
  headerData[key] = value;
78512
78687
  });
@@ -78518,17 +78693,15 @@ var HonoRequest = class {
78518
78693
  #cachedBody = (key) => {
78519
78694
  const { bodyCache, raw } = this;
78520
78695
  const cachedBody = bodyCache[key];
78521
- if (cachedBody) {
78696
+ if (cachedBody)
78522
78697
  return cachedBody;
78523
- }
78524
- for (const anyCachedKey in bodyCache) {
78698
+ for (const anyCachedKey in bodyCache)
78525
78699
  return bodyCache[anyCachedKey].then((body) => {
78526
- if (anyCachedKey === "json") {
78700
+ if (anyCachedKey === "json")
78527
78701
  body = JSON.stringify(body);
78528
- }
78529
- return new Response(body)[key]();
78702
+ const contentType = anyCachedKey === "formData" ? undefined : raw.headers.get("content-type");
78703
+ return new Response(body, { headers: contentType ? { "Content-Type": contentType } : undefined })[key]();
78530
78704
  });
78531
- }
78532
78705
  return bodyCache[key] = raw[key]();
78533
78706
  };
78534
78707
  json() {
@@ -78572,7 +78745,7 @@ var HonoRequest = class {
78572
78745
  }
78573
78746
  };
78574
78747
 
78575
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/html.js
78748
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/html.js
78576
78749
  var HtmlEscapedCallbackPhase = {
78577
78750
  Stringify: 1,
78578
78751
  BeforeStream: 2,
@@ -78586,31 +78759,30 @@ var raw = (value, callbacks) => {
78586
78759
  };
78587
78760
  var resolveCallback = async (str, phase, preserveCallbacks, context, buffer) => {
78588
78761
  if (typeof str === "object" && !(str instanceof String)) {
78589
- if (!(str instanceof Promise)) {
78762
+ if (!(str instanceof Promise))
78590
78763
  str = str.toString();
78591
- }
78592
- if (str instanceof Promise) {
78764
+ if (str instanceof Promise)
78593
78765
  str = await str;
78594
- }
78595
78766
  }
78596
78767
  const callbacks = str.callbacks;
78597
- if (!callbacks?.length) {
78768
+ if (!callbacks?.length)
78598
78769
  return Promise.resolve(str);
78599
- }
78600
- if (buffer) {
78770
+ if (buffer)
78601
78771
  buffer[0] += str;
78602
- } else {
78772
+ else
78603
78773
  buffer = [str];
78604
- }
78605
- const resStr = Promise.all(callbacks.map((c) => c({ phase, buffer, context }))).then((res) => Promise.all(res.filter(Boolean).map((str2) => resolveCallback(str2, phase, false, context, buffer))).then(() => buffer[0]));
78606
- if (preserveCallbacks) {
78774
+ const resStr = Promise.all(callbacks.map((c) => c({
78775
+ phase,
78776
+ buffer,
78777
+ context
78778
+ }))).then((res) => Promise.all(res.filter(Boolean).map((str) => resolveCallback(str, phase, false, context, buffer))).then(() => buffer[0]));
78779
+ if (preserveCallbacks)
78607
78780
  return raw(await resStr, callbacks);
78608
- } else {
78781
+ else
78609
78782
  return resStr;
78610
- }
78611
78783
  };
78612
78784
 
78613
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/context.js
78785
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/context.js
78614
78786
  var TEXT_PLAIN = "text/plain; charset=UTF-8";
78615
78787
  var setDefaultContentType = (contentType, headers) => {
78616
78788
  return {
@@ -78650,40 +78822,33 @@ var Context = class {
78650
78822
  return this.#req;
78651
78823
  }
78652
78824
  get event() {
78653
- if (this.#executionCtx && "respondWith" in this.#executionCtx) {
78825
+ if (this.#executionCtx && "respondWith" in this.#executionCtx)
78654
78826
  return this.#executionCtx;
78655
- } else {
78827
+ else
78656
78828
  throw Error("This context has no FetchEvent");
78657
- }
78658
78829
  }
78659
78830
  get executionCtx() {
78660
- if (this.#executionCtx) {
78831
+ if (this.#executionCtx)
78661
78832
  return this.#executionCtx;
78662
- } else {
78833
+ else
78663
78834
  throw Error("This context has no ExecutionContext");
78664
- }
78665
78835
  }
78666
78836
  get res() {
78667
- return this.#res ||= createResponseInstance(null, {
78668
- headers: this.#preparedHeaders ??= new Headers
78669
- });
78837
+ return this.#res ||= createResponseInstance(null, { headers: this.#preparedHeaders ??= new Headers });
78670
78838
  }
78671
78839
  set res(_res) {
78672
78840
  if (this.#res && _res) {
78673
78841
  _res = createResponseInstance(_res.body, _res);
78674
78842
  for (const [k, v] of this.#res.headers.entries()) {
78675
- if (k === "content-type") {
78843
+ if (k === "content-type")
78676
78844
  continue;
78677
- }
78678
78845
  if (k === "set-cookie") {
78679
78846
  const cookies = this.#res.headers.getSetCookie();
78680
78847
  _res.headers.delete("set-cookie");
78681
- for (const cookie of cookies) {
78848
+ for (const cookie of cookies)
78682
78849
  _res.headers.append("set-cookie", cookie);
78683
- }
78684
- } else {
78850
+ } else
78685
78851
  _res.headers.set(k, v);
78686
- }
78687
78852
  }
78688
78853
  }
78689
78854
  this.#res = _res;
@@ -78699,17 +78864,15 @@ var Context = class {
78699
78864
  this.#renderer = renderer;
78700
78865
  };
78701
78866
  header = (name, value, options) => {
78702
- if (this.finalized) {
78867
+ if (this.finalized)
78703
78868
  this.#res = createResponseInstance(this.#res.body, this.#res);
78704
- }
78705
78869
  const headers = this.#res ? this.#res.headers : this.#preparedHeaders ??= new Headers;
78706
- if (value === undefined) {
78870
+ if (value === undefined)
78707
78871
  headers.delete(name);
78708
- } else if (options?.append) {
78872
+ else if (options?.append)
78709
78873
  headers.append(name, value);
78710
- } else {
78874
+ else
78711
78875
  headers.set(name, value);
78712
- }
78713
78876
  };
78714
78877
  status = (status) => {
78715
78878
  this.#status = status;
@@ -78722,46 +78885,40 @@ var Context = class {
78722
78885
  return this.#var ? this.#var.get(key) : undefined;
78723
78886
  };
78724
78887
  get var() {
78725
- if (!this.#var) {
78888
+ if (!this.#var)
78726
78889
  return {};
78727
- }
78728
78890
  return Object.fromEntries(this.#var);
78729
78891
  }
78730
78892
  #newResponse(data, arg, headers) {
78731
78893
  let responseHeaders = this.#res ? new Headers(this.#res.headers) : this.#preparedHeaders;
78732
78894
  if (typeof arg === "object" && arg.headers) {
78733
78895
  responseHeaders ??= new Headers;
78734
- for (const [key, value] of new Headers(arg.headers)) {
78735
- if (key === "set-cookie") {
78896
+ for (const [key, value] of new Headers(arg.headers))
78897
+ if (key === "set-cookie")
78736
78898
  responseHeaders.append(key, value);
78737
- } else {
78899
+ else
78738
78900
  responseHeaders.set(key, value);
78739
- }
78740
- }
78741
78901
  }
78742
78902
  if (headers) {
78743
78903
  if (!responseHeaders) {
78744
78904
  let count = 0;
78745
- for (const k in headers) {
78905
+ for (const k in headers)
78746
78906
  if (++count > 1 || typeof headers[k] !== "string") {
78747
78907
  responseHeaders = new Headers;
78748
78908
  break;
78749
78909
  }
78750
- }
78751
78910
  }
78752
- if (responseHeaders) {
78911
+ if (responseHeaders)
78753
78912
  for (const k in headers) {
78754
78913
  const v = headers[k];
78755
- if (typeof v === "string") {
78914
+ if (typeof v === "string")
78756
78915
  responseHeaders.set(k, v);
78757
- } else {
78916
+ else {
78758
78917
  responseHeaders.delete(k);
78759
- for (const v2 of v) {
78918
+ for (const v2 of v)
78760
78919
  responseHeaders.append(k, v2);
78761
- }
78762
78920
  }
78763
78921
  }
78764
- }
78765
78922
  }
78766
78923
  const status = typeof arg === "number" ? arg : arg?.status ?? this.#status;
78767
78924
  return createResponseInstance(data, {
@@ -78792,18 +78949,61 @@ var Context = class {
78792
78949
  };
78793
78950
  };
78794
78951
 
78795
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router.js
78796
- var METHOD_NAME_ALL = "ALL";
78797
- var METHOD_NAME_ALL_LOWERCASE = "all";
78798
- var METHODS = ["get", "post", "put", "delete", "options", "patch", "query"];
78952
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/compose.js
78953
+ var compose = (middleware, onError, onNotFound) => {
78954
+ return (context, next) => {
78955
+ let index = -1;
78956
+ return dispatch(0);
78957
+ async function dispatch(i) {
78958
+ if (i <= index)
78959
+ throw new Error("next() called multiple times");
78960
+ index = i;
78961
+ let res;
78962
+ let isError = false;
78963
+ let handler;
78964
+ if (middleware[i]) {
78965
+ handler = middleware[i][0][0];
78966
+ context.req.routeIndex = i;
78967
+ } else
78968
+ handler = i === middleware.length && next || undefined;
78969
+ if (handler)
78970
+ try {
78971
+ res = await handler(context, () => dispatch(i + 1));
78972
+ } catch (err) {
78973
+ if (err instanceof Error && onError) {
78974
+ context.error = err;
78975
+ res = await onError(err, context);
78976
+ isError = true;
78977
+ } else
78978
+ throw err;
78979
+ }
78980
+ else if (context.finalized === false && onNotFound)
78981
+ res = await onNotFound(context);
78982
+ if (res && (context.finalized === false || isError))
78983
+ context.res = res;
78984
+ return context;
78985
+ }
78986
+ };
78987
+ };
78988
+
78989
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router.js
78990
+ var METHODS = [
78991
+ "get",
78992
+ "post",
78993
+ "put",
78994
+ "delete",
78995
+ "options",
78996
+ "patch",
78997
+ "query"
78998
+ ];
78799
78999
  var MESSAGE_MATCHER_IS_ALREADY_BUILT = "Can not add a route since the matcher is already built.";
78800
79000
  var UnsupportedPathError = class extends Error {
78801
79001
  };
78802
79002
 
78803
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/constants.js
79003
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/constants.js
78804
79004
  var COMPOSED_HANDLER = "__COMPOSED_HANDLER";
78805
79005
 
78806
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/hono-base.js
79006
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/hono-base.js
78807
79007
  var notFoundHandler = (c) => {
78808
79008
  return c.text("404 Not Found", 404);
78809
79009
  };
@@ -78815,7 +79015,7 @@ var errorHandler = (err, c) => {
78815
79015
  console.error(err);
78816
79016
  return c.text("Internal Server Error", 500);
78817
79017
  };
78818
- var Hono = class _Hono {
79018
+ var Hono = class Hono {
78819
79019
  get;
78820
79020
  post;
78821
79021
  put;
@@ -78832,15 +79032,13 @@ var Hono = class _Hono {
78832
79032
  #path = "/";
78833
79033
  routes = [];
78834
79034
  constructor(options = {}) {
78835
- const allMethods = [...METHODS, METHOD_NAME_ALL_LOWERCASE];
78836
- allMethods.forEach((method) => {
79035
+ [...METHODS, "all"].forEach((method) => {
78837
79036
  this[method] = (args1, ...args) => {
78838
79037
  const methodName = method.toUpperCase();
78839
- if (typeof args1 === "string") {
79038
+ if (typeof args1 === "string")
78840
79039
  this.#path = args1;
78841
- } else {
79040
+ else
78842
79041
  this.#addRoute(methodName, this.#path, args1);
78843
- }
78844
79042
  args.forEach((handler) => {
78845
79043
  this.#addRoute(methodName, this.#path, handler);
78846
79044
  });
@@ -78852,22 +79050,21 @@ var Hono = class _Hono {
78852
79050
  this.#path = p;
78853
79051
  for (const m of [method].flat()) {
78854
79052
  const methodName = m.toUpperCase();
78855
- for (const handler of handlers) {
79053
+ for (const handler of handlers)
78856
79054
  this.#addRoute(methodName, this.#path, handler);
78857
- }
78858
79055
  }
78859
79056
  }
78860
79057
  return this;
78861
79058
  };
78862
79059
  this.use = (arg1, ...handlers) => {
78863
- if (typeof arg1 === "string") {
79060
+ if (typeof arg1 === "string")
78864
79061
  this.#path = arg1;
78865
- } else {
79062
+ else {
78866
79063
  this.#path = "*";
78867
79064
  handlers.unshift(arg1);
78868
79065
  }
78869
79066
  handlers.forEach((handler) => {
78870
- this.#addRoute(METHOD_NAME_ALL, this.#path, handler);
79067
+ this.#addRoute("ALL", this.#path, handler);
78871
79068
  });
78872
79069
  return this;
78873
79070
  };
@@ -78876,7 +79073,7 @@ var Hono = class _Hono {
78876
79073
  this.getPath = strict ?? true ? options.getPath ?? getPath : getPathNoStrict;
78877
79074
  }
78878
79075
  #clone() {
78879
- const clone = new _Hono({
79076
+ const clone = new Hono({
78880
79077
  router: this.router,
78881
79078
  getPath: this.getPath
78882
79079
  });
@@ -78891,9 +79088,9 @@ var Hono = class _Hono {
78891
79088
  const subApp = this.basePath(path);
78892
79089
  app.routes.map((r) => {
78893
79090
  let handler;
78894
- if (app.errorHandler === errorHandler) {
79091
+ if (app.errorHandler === errorHandler)
78895
79092
  handler = r.handler;
78896
- } else {
79093
+ else {
78897
79094
  handler = async (c, next) => (await compose([], app.errorHandler)(c, () => r.handler(c, next))).res;
78898
79095
  handler[COMPOSED_HANDLER] = r.handler;
78899
79096
  }
@@ -78918,20 +79115,19 @@ var Hono = class _Hono {
78918
79115
  let replaceRequest;
78919
79116
  let optionHandler;
78920
79117
  if (options) {
78921
- if (typeof options === "function") {
79118
+ if (typeof options === "function")
78922
79119
  optionHandler = options;
78923
- } else {
79120
+ else {
78924
79121
  optionHandler = options.optionHandler;
78925
- if (options.replaceRequest === false) {
79122
+ if (options.replaceRequest === false)
78926
79123
  replaceRequest = (request) => request;
78927
- } else {
79124
+ else
78928
79125
  replaceRequest = options.replaceRequest;
78929
- }
78930
79126
  }
78931
79127
  }
78932
79128
  const getOptions = optionHandler ? (c) => {
78933
- const options2 = optionHandler(c);
78934
- return Array.isArray(options2) ? options2 : [options2];
79129
+ const options = optionHandler(c);
79130
+ return Array.isArray(options) ? options : [options];
78935
79131
  } : (c) => {
78936
79132
  let executionContext = undefined;
78937
79133
  try {
@@ -78950,12 +79146,11 @@ var Hono = class _Hono {
78950
79146
  })();
78951
79147
  const handler = async (c, next) => {
78952
79148
  const res = await applicationHandler(replaceRequest(c.req.raw), ...getOptions(c));
78953
- if (res) {
79149
+ if (res)
78954
79150
  return res;
78955
- }
78956
79151
  await next();
78957
79152
  };
78958
- this.#addRoute(METHOD_NAME_ALL, mergePath(path, "*"), handler);
79153
+ this.#addRoute("ALL", mergePath(path, "*"), handler);
78959
79154
  return this;
78960
79155
  }
78961
79156
  #addRoute(method, path, handler, baseRoutePath) {
@@ -78970,15 +79165,13 @@ var Hono = class _Hono {
78970
79165
  this.routes.push(r);
78971
79166
  }
78972
79167
  #handleError(err, c) {
78973
- if (err instanceof Error) {
79168
+ if (err instanceof Error)
78974
79169
  return this.errorHandler(err, c);
78975
- }
78976
79170
  throw err;
78977
79171
  }
78978
79172
  #dispatch(request, executionCtx, env, method) {
78979
- if (method === "HEAD") {
79173
+ if (method === "HEAD")
78980
79174
  return (async () => new Response(null, await this.#dispatch(request, executionCtx, env, "GET")))();
78981
- }
78982
79175
  const path = this.getPath(request, { env });
78983
79176
  const matchResult = this.router.match(method, path);
78984
79177
  const c = new Context(request, {
@@ -79003,9 +79196,8 @@ var Hono = class _Hono {
79003
79196
  return (async () => {
79004
79197
  try {
79005
79198
  const context = await composed(c);
79006
- if (!context.finalized) {
79199
+ if (!context.finalized)
79007
79200
  throw new Error("Context is not finalized. Did you forget to return a Response object or `await next()`?");
79008
- }
79009
79201
  return context.res;
79010
79202
  } catch (err) {
79011
79203
  return this.#handleError(err, c);
@@ -79016,9 +79208,8 @@ var Hono = class _Hono {
79016
79208
  return this.#dispatch(request, rest[1], rest[0], request.method);
79017
79209
  };
79018
79210
  request = (input, requestInit, Env, executionCtx) => {
79019
- if (input instanceof Request) {
79211
+ if (input instanceof Request)
79020
79212
  return this.fetch(requestInit ? new Request(input, requestInit) : input, Env, executionCtx);
79021
- }
79022
79213
  input = input.toString();
79023
79214
  return this.fetch(new Request(/^https?:\/\//.test(input) ? input : `http://localhost${mergePath("/", input)}`, requestInit), Env, executionCtx);
79024
79215
  };
@@ -79029,56 +79220,49 @@ var Hono = class _Hono {
79029
79220
  };
79030
79221
  };
79031
79222
 
79032
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/utils.js
79033
- var createNullObject = () => /* @__PURE__ */ Object.create(null);
79223
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router/utils.js
79224
+ var createNullObject = () => Object.create(null);
79034
79225
 
79035
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/matcher.js
79226
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router/reg-exp-router/matcher.js
79036
79227
  var emptyParam = [];
79037
79228
  function match(method, path) {
79038
79229
  const matchers = this.buildAllMatchers();
79039
- const match2 = (method2, path2) => {
79040
- const matcher = matchers[method2] || matchers[METHOD_NAME_ALL];
79041
- const staticMatch = matcher[2][path2];
79042
- if (staticMatch) {
79230
+ const match = (method, path) => {
79231
+ const matcher = matchers[method] || matchers["ALL"];
79232
+ const staticMatch = matcher[2][path];
79233
+ if (staticMatch)
79043
79234
  return staticMatch;
79044
- }
79045
- const match3 = path2.match(matcher[0]);
79046
- if (!match3) {
79235
+ const match = path.match(matcher[0]);
79236
+ if (!match)
79047
79237
  return [[], emptyParam];
79048
- }
79049
- const index = match3.indexOf("", 1);
79050
- return [matcher[1][index], match3];
79238
+ const index = match.indexOf("", 1);
79239
+ return [matcher[1][index], match];
79051
79240
  };
79052
- this.match = match2;
79053
- return match2(method, path);
79241
+ this.match = match;
79242
+ return match(method, path);
79054
79243
  }
79055
79244
 
79056
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/node.js
79245
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router/reg-exp-router/node.js
79057
79246
  var LABEL_REG_EXP_STR = "[^/]+";
79058
- var ONLY_WILDCARD_REG_EXP_STR = ".*";
79059
79247
  var TAIL_WILDCARD_REG_EXP_STR = "(?:|/.*)";
79060
- var PATH_ERROR = /* @__PURE__ */ Symbol();
79061
- var regExpMetaChars = new Set(".\\+*[^]$()");
79248
+ var PATH_ERROR = Symbol();
79249
+ var regExpMetaChars = /* @__PURE__ */ new Set(".\\+*[^]$()");
79062
79250
  function compareKey(a, b) {
79063
- if (a.length === 1) {
79251
+ if (a.length === 1)
79064
79252
  return b.length === 1 ? a < b ? -1 : 1 : -1;
79065
- }
79066
- if (b.length === 1) {
79253
+ if (b.length === 1)
79067
79254
  return 1;
79068
- }
79069
- if (a === ONLY_WILDCARD_REG_EXP_STR || a === TAIL_WILDCARD_REG_EXP_STR) {
79070
- return b === TAIL_WILDCARD_REG_EXP_STR ? -1 : 1;
79071
- } else if (b === ONLY_WILDCARD_REG_EXP_STR || b === TAIL_WILDCARD_REG_EXP_STR) {
79255
+ if (a === ".*" || a === "(?:|/.*)")
79256
+ return b === "(?:|/.*)" ? -1 : 1;
79257
+ else if (b === ".*" || b === "(?:|/.*)")
79072
79258
  return -1;
79073
- }
79074
- if (a === LABEL_REG_EXP_STR) {
79259
+ if (a === "[^/]+")
79075
79260
  return 1;
79076
- } else if (b === LABEL_REG_EXP_STR) {
79261
+ else if (b === "[^/]+")
79077
79262
  return -1;
79078
- }
79079
79263
  return a.length === b.length ? a < b ? -1 : 1 : b.length - a.length;
79080
79264
  }
79081
- var Node = class _Node {
79265
+ var Node = class Node {
79082
79266
  #index;
79083
79267
  #varIndex;
79084
79268
  #children = createNullObject();
@@ -79086,33 +79270,40 @@ var Node = class _Node {
79086
79270
  let node = this;
79087
79271
  for (let i = 0, len = tokens.length;i < len; i++) {
79088
79272
  const token = tokens[i];
79089
- const pattern = token.length === 1 ? token === "*" ? i === len - 1 ? ["", "", ONLY_WILDCARD_REG_EXP_STR] : ["", "", LABEL_REG_EXP_STR] : null : token === "/*" ? ["", "", TAIL_WILDCARD_REG_EXP_STR] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
79273
+ const pattern = token.length === 1 ? token === "*" ? i === len - 1 ? [
79274
+ "",
79275
+ "",
79276
+ ".*"
79277
+ ] : [
79278
+ "",
79279
+ "",
79280
+ LABEL_REG_EXP_STR
79281
+ ] : null : token === "/*" ? [
79282
+ "",
79283
+ "",
79284
+ TAIL_WILDCARD_REG_EXP_STR
79285
+ ] : token.match(/^\:([^\{\}]+)(?:\{(.+)\})?$/);
79090
79286
  let nextNode;
79091
79287
  if (pattern) {
79092
79288
  const name = pattern[1];
79093
- let regexpStr = pattern[2] || LABEL_REG_EXP_STR;
79289
+ let regexpStr = pattern[2] || "[^/]+";
79094
79290
  if (name && pattern[2]) {
79095
- if (regexpStr === ".*") {
79291
+ if (regexpStr === ".*")
79096
79292
  throw PATH_ERROR;
79097
- }
79098
79293
  regexpStr = regexpStr.replace(/^\((?!\?:)(?=[^)]+\)$)/, "(?:");
79099
- if (/\((?!\?:)/.test(regexpStr)) {
79294
+ if (/\((?!\?:)/.test(regexpStr))
79100
79295
  throw PATH_ERROR;
79101
- }
79102
- if (regexpStr.length === 1 && regExpMetaChars.has(regexpStr)) {
79296
+ if (regexpStr.length === 1 && regExpMetaChars.has(regexpStr))
79103
79297
  throw PATH_ERROR;
79104
- }
79105
79298
  }
79106
79299
  nextNode = node.#children[regexpStr];
79107
79300
  if (!nextNode) {
79108
- if (regexpStr !== ONLY_WILDCARD_REG_EXP_STR && regexpStr !== TAIL_WILDCARD_REG_EXP_STR) {
79109
- for (const k in node.#children) {
79110
- if ((regexpStr.length > 1 || k.length > 1) && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR) {
79301
+ if (regexpStr !== ".*" && regexpStr !== "(?:|/.*)") {
79302
+ for (const k in node.#children)
79303
+ if ((regexpStr.length > 1 || k.length > 1) && k !== ".*" && k !== "(?:|/.*)")
79111
79304
  throw PATH_ERROR;
79112
- }
79113
- }
79114
79305
  }
79115
- nextNode = node.#children[regexpStr] = new _Node;
79306
+ nextNode = node.#children[regexpStr] = new Node;
79116
79307
  }
79117
79308
  if (name !== "") {
79118
79309
  nextNode.#varIndex ??= context.varIndex++;
@@ -79121,42 +79312,35 @@ var Node = class _Node {
79121
79312
  } else {
79122
79313
  nextNode = node.#children[token];
79123
79314
  if (!nextNode) {
79124
- for (const k in node.#children) {
79125
- if (k.length > 1 && k !== ONLY_WILDCARD_REG_EXP_STR && k !== TAIL_WILDCARD_REG_EXP_STR) {
79315
+ for (const k in node.#children)
79316
+ if (k.length > 1 && k !== ".*" && k !== "(?:|/.*)")
79126
79317
  throw PATH_ERROR;
79127
- }
79128
- }
79129
- nextNode = node.#children[token] = new _Node;
79318
+ nextNode = node.#children[token] = new Node;
79130
79319
  }
79131
79320
  }
79132
79321
  node = nextNode;
79133
79322
  }
79134
- if (node.#index !== undefined) {
79323
+ if (node.#index !== undefined)
79135
79324
  throw PATH_ERROR;
79136
- }
79137
79325
  node.#index = isStatic ? -1 : index;
79138
79326
  }
79139
79327
  buildRegExpStr() {
79140
- const childKeys = Object.keys(this.#children).sort(compareKey);
79141
- const strList = childKeys.map((k) => {
79328
+ const strList = Object.keys(this.#children).sort(compareKey).map((k) => {
79142
79329
  const c = this.#children[k];
79143
79330
  const childStr = c.buildRegExpStr();
79144
79331
  return childStr === "" ? "" : (typeof c.#varIndex === "number" ? `(${k})@${c.#varIndex}` : regExpMetaChars.has(k) ? `\\${k}` : k) + childStr;
79145
79332
  }).filter(Boolean);
79146
- if (typeof this.#index === "number" && this.#index !== -1) {
79333
+ if (typeof this.#index === "number" && this.#index !== -1)
79147
79334
  strList.unshift(`#${this.#index}`);
79148
- }
79149
- if (strList.length === 0) {
79335
+ if (strList.length === 0)
79150
79336
  return "";
79151
- }
79152
- if (strList.length === 1) {
79337
+ if (strList.length === 1)
79153
79338
  return strList[0];
79154
- }
79155
79339
  return "(?:" + strList.join("|") + ")";
79156
79340
  }
79157
79341
  };
79158
79342
 
79159
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/trie.js
79343
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router/reg-exp-router/trie.js
79160
79344
  var Trie = class {
79161
79345
  #context = { varIndex: 0 };
79162
79346
  #root = new Node;
@@ -79179,28 +79363,29 @@ var Trie = class {
79179
79363
  replaced = true;
79180
79364
  return mark;
79181
79365
  });
79182
- if (!replaced) {
79366
+ if (!replaced)
79183
79367
  break;
79184
- }
79185
79368
  }
79186
79369
  const tokens = markedPath.match(/(?::[^\/]+)|(?:\/\*$)|./g) || [];
79187
79370
  for (let i = groups.length - 1;i >= 0; i--) {
79188
79371
  const [mark] = groups[i];
79189
- for (let j = tokens.length - 1;j >= 0; j--) {
79372
+ for (let j = tokens.length - 1;j >= 0; j--)
79190
79373
  if (tokens[j].indexOf(mark) !== -1) {
79191
79374
  tokens[j] = tokens[j].replace(mark, groups[i][1]);
79192
79375
  break;
79193
79376
  }
79194
- }
79195
79377
  }
79196
79378
  this.#root.insert(tokens, this.#index, paramAssoc, this.#context, false);
79197
79379
  this.paths[path] = [this.#index++, paramAssoc];
79198
79380
  }
79199
79381
  buildRegExp() {
79200
79382
  let regexp = this.#root.buildRegExpStr();
79201
- if (regexp === "") {
79202
- return [/^$/, [], []];
79203
- }
79383
+ if (regexp === "")
79384
+ return [
79385
+ /^$/,
79386
+ [],
79387
+ []
79388
+ ];
79204
79389
  let captureIndex = 0;
79205
79390
  const indexReplacementMap = [];
79206
79391
  const paramReplacementMap = [];
@@ -79215,22 +79400,23 @@ var Trie = class {
79215
79400
  }
79216
79401
  return "";
79217
79402
  });
79218
- return [new RegExp(`^${regexp}`), indexReplacementMap, paramReplacementMap];
79403
+ return [
79404
+ new RegExp(`^${regexp}`),
79405
+ indexReplacementMap,
79406
+ paramReplacementMap
79407
+ ];
79219
79408
  }
79220
79409
  };
79221
79410
 
79222
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/reg-exp-router/router.js
79411
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router/reg-exp-router/router.js
79223
79412
  var wildcardRegExpCache = createNullObject();
79224
79413
  function buildWildcardRegExp(path) {
79225
- return wildcardRegExpCache[path] ??= new RegExp(`^${path.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match2, metaChar) => metaChar ? `\\${metaChar}` : match2 === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match2 === "*" ? ONLY_WILDCARD_REG_EXP_STR : `/:${LABEL_REG_EXP_STR}`)}$`);
79414
+ return wildcardRegExpCache[path] ??= new RegExp(`^${path.replace(/\/:[^/{}]+(?:\{\[\^\/]\+})?(?=[/{]|$)|\/?\*$|([.\\+*[^\]$()?{}|])/g, (match, metaChar) => metaChar ? `\\${metaChar}` : match === "/*" ? TAIL_WILDCARD_REG_EXP_STR : match === "*" ? ".*" : `/:${LABEL_REG_EXP_STR}`)}$`);
79226
79415
  }
79227
79416
  function findMiddleware(middleware, path) {
79228
- for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length)) {
79229
- if (buildWildcardRegExp(k).test(path)) {
79417
+ for (const k of Object.keys(middleware).sort((a, b) => b.length - a.length))
79418
+ if (buildWildcardRegExp(k).test(path))
79230
79419
  return [...middleware[k]];
79231
- }
79232
- }
79233
- return;
79234
79420
  }
79235
79421
  var RegExpRouter = class {
79236
79422
  name = "RegExpRouter";
@@ -79238,9 +79424,9 @@ var RegExpRouter = class {
79238
79424
  #routes;
79239
79425
  #tries;
79240
79426
  constructor() {
79241
- this.#middleware = { [METHOD_NAME_ALL]: createNullObject() };
79242
- this.#routes = { [METHOD_NAME_ALL]: createNullObject() };
79243
- this.#tries = { [METHOD_NAME_ALL]: new Trie };
79427
+ this.#middleware = { ["ALL"]: createNullObject() };
79428
+ this.#routes = { ["ALL"]: createNullObject() };
79429
+ this.#tries = { ["ALL"]: new Trie };
79244
79430
  }
79245
79431
  #insertPath(method, path) {
79246
79432
  try {
@@ -79252,57 +79438,49 @@ var RegExpRouter = class {
79252
79438
  add(method, path, handler) {
79253
79439
  const middleware = this.#middleware;
79254
79440
  const routes = this.#routes;
79255
- if (!middleware) {
79441
+ if (!middleware)
79256
79442
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
79257
- }
79258
79443
  if (!middleware[method]) {
79259
79444
  this.#tries[method] = new Trie;
79260
79445
  for (const handlerMap of [middleware, routes]) {
79261
79446
  handlerMap[method] = createNullObject();
79262
- for (const p in handlerMap[METHOD_NAME_ALL]) {
79263
- handlerMap[method][p] = [...handlerMap[METHOD_NAME_ALL][p]];
79447
+ for (const p in handlerMap["ALL"]) {
79448
+ handlerMap[method][p] = [...handlerMap["ALL"][p]];
79264
79449
  this.#insertPath(method, p);
79265
79450
  }
79266
79451
  }
79267
79452
  }
79268
- if (path === "/*") {
79453
+ if (path === "/*")
79269
79454
  path = "*";
79270
- }
79271
- const methods = method === METHOD_NAME_ALL ? Object.keys(middleware) : [method];
79455
+ const methods = method === "ALL" ? Object.keys(middleware) : [method];
79272
79456
  if (/\*$/.test(path)) {
79273
79457
  const re = buildWildcardRegExp(path);
79274
- for (const m of methods) {
79458
+ for (const m of methods)
79275
79459
  if (!middleware[m][path]) {
79276
79460
  this.#insertPath(m, path);
79277
- middleware[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware[METHOD_NAME_ALL], path) || [];
79461
+ middleware[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware["ALL"], path) || [];
79278
79462
  }
79279
- }
79280
- for (const handlerMap of [middleware, routes]) {
79281
- for (const m of methods) {
79282
- for (const p in handlerMap[m]) {
79463
+ for (const handlerMap of [middleware, routes])
79464
+ for (const m of methods)
79465
+ for (const p in handlerMap[m])
79283
79466
  re.test(p) && handlerMap[m][p].push([handler, path]);
79284
- }
79285
- }
79286
- }
79287
79467
  return;
79288
79468
  }
79289
79469
  const paths = checkOptionalParameter(path) || [path];
79290
- for (const path2 of paths) {
79470
+ for (const path of paths)
79291
79471
  for (const m of methods) {
79292
- if (!routes[m][path2]) {
79293
- this.#insertPath(m, path2);
79294
- routes[m][path2] = findMiddleware(middleware[m], path2) || findMiddleware(middleware[METHOD_NAME_ALL], path2) || [];
79472
+ if (!routes[m][path]) {
79473
+ this.#insertPath(m, path);
79474
+ routes[m][path] = findMiddleware(middleware[m], path) || findMiddleware(middleware["ALL"], path) || [];
79295
79475
  }
79296
- routes[m][path2].push([handler, path2]);
79476
+ routes[m][path].push([handler, path]);
79297
79477
  }
79298
- }
79299
79478
  }
79300
79479
  match = match;
79301
79480
  buildAllMatchers() {
79302
79481
  const matchers = createNullObject();
79303
- for (const method of Object.keys(this.#routes)) {
79482
+ for (const method of Object.keys(this.#routes))
79304
79483
  matchers[method] = this.#buildMatcher(method);
79305
- }
79306
79484
  this.#middleware = this.#routes = this.#tries = undefined;
79307
79485
  wildcardRegExpCache = createNullObject();
79308
79486
  return matchers;
@@ -79314,7 +79492,7 @@ var RegExpRouter = class {
79314
79492
  const staticMap = createNullObject();
79315
79493
  const handlerData = [];
79316
79494
  const [regexp, indexReplacementMap, paramReplacementMap] = trie.buildRegExp();
79317
- for (const r of [middleware, routes]) {
79495
+ for (const r of [middleware, routes])
79318
79496
  for (const path in r) {
79319
79497
  const handlers = r[path];
79320
79498
  const pathData = trie.paths[path];
@@ -79322,20 +79500,20 @@ var RegExpRouter = class {
79322
79500
  staticMap[path] = [handlers.map(([h]) => [h, createNullObject()]), emptyParam];
79323
79501
  continue;
79324
79502
  }
79325
- handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [
79326
- h,
79327
- trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
79328
- map[key] = paramReplacementMap[pathData[1][i][1]];
79329
- return map;
79330
- }, createNullObject())
79331
- ]);
79503
+ handlerData[pathData[0]] = handlers.map(([h, handlerPath]) => [h, trie.paths[handlerPath][1].reduceRight((map, [key], i) => {
79504
+ map[key] = paramReplacementMap[pathData[1][i][1]];
79505
+ return map;
79506
+ }, createNullObject())]);
79332
79507
  }
79333
- }
79334
- return [regexp, indexReplacementMap.map((i) => handlerData[i]), staticMap];
79508
+ return [
79509
+ regexp,
79510
+ indexReplacementMap.map((i) => handlerData[i]),
79511
+ staticMap
79512
+ ];
79335
79513
  }
79336
79514
  };
79337
79515
 
79338
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/smart-router/router.js
79516
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router/smart-router/router.js
79339
79517
  var SmartRouter = class {
79340
79518
  name = "SmartRouter";
79341
79519
  #routers = [];
@@ -79344,15 +79522,17 @@ var SmartRouter = class {
79344
79522
  this.#routers = init.routers;
79345
79523
  }
79346
79524
  add(method, path, handler) {
79347
- if (!this.#routes) {
79525
+ if (!this.#routes)
79348
79526
  throw new Error(MESSAGE_MATCHER_IS_ALREADY_BUILT);
79349
- }
79350
- this.#routes.push([method, path, handler]);
79527
+ this.#routes.push([
79528
+ method,
79529
+ path,
79530
+ handler
79531
+ ]);
79351
79532
  }
79352
79533
  match(method, path) {
79353
- if (!this.#routes) {
79534
+ if (!this.#routes)
79354
79535
  throw new Error("Fatal error");
79355
- }
79356
79536
  const routers = this.#routers;
79357
79537
  const routes = this.#routes;
79358
79538
  const len = routers.length;
@@ -79361,14 +79541,12 @@ var SmartRouter = class {
79361
79541
  for (;i < len; i++) {
79362
79542
  const router = routers[i];
79363
79543
  try {
79364
- for (let i2 = 0, len2 = routes.length;i2 < len2; i2++) {
79365
- router.add(...routes[i2]);
79366
- }
79544
+ for (let i = 0, len = routes.length;i < len; i++)
79545
+ router.add(...routes[i]);
79367
79546
  res = router.match(method, path);
79368
79547
  } catch (e) {
79369
- if (e instanceof UnsupportedPathError) {
79548
+ if (e instanceof UnsupportedPathError)
79370
79549
  continue;
79371
- }
79372
79550
  throw e;
79373
79551
  }
79374
79552
  this.match = router.match.bind(router);
@@ -79376,24 +79554,22 @@ var SmartRouter = class {
79376
79554
  this.#routes = undefined;
79377
79555
  break;
79378
79556
  }
79379
- if (i === len) {
79557
+ if (i === len)
79380
79558
  throw new Error("Fatal error");
79381
- }
79382
79559
  this.name = `SmartRouter + ${this.activeRouter.name}`;
79383
79560
  return res;
79384
79561
  }
79385
79562
  get activeRouter() {
79386
- if (this.#routes || this.#routers.length !== 1) {
79563
+ if (this.#routes || this.#routers.length !== 1)
79387
79564
  throw new Error("No active router has been determined yet.");
79388
- }
79389
79565
  return this.#routers[0];
79390
79566
  }
79391
79567
  };
79392
79568
 
79393
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/trie-router/node.js
79569
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router/trie-router/node.js
79394
79570
  var emptyParams = createNullObject();
79395
79571
  var order = 0;
79396
- var Node2 = class _Node {
79572
+ var Node2 = class Node {
79397
79573
  #methods = [];
79398
79574
  #children = createNullObject();
79399
79575
  #patterns = [];
@@ -79409,34 +79585,31 @@ var Node2 = class _Node {
79409
79585
  const pattern = getPattern(p, nextP) || (nextP === undefined && p && p.indexOf("*") === p.length - 1 ? p : null);
79410
79586
  const isParam = Array.isArray(pattern);
79411
79587
  const key = isParam ? pattern[0] : pattern || p;
79412
- const child = curNode.#children[key] ||= new _Node;
79588
+ const child = curNode.#children[key] ||= new Node;
79413
79589
  if (pattern && !child.#pattern) {
79414
79590
  child.#pattern = pattern;
79415
79591
  curNode.#patterns.push(child);
79416
79592
  }
79417
79593
  curNode = child;
79418
- if (isParam) {
79594
+ if (isParam)
79419
79595
  possibleKeys.add(pattern[1]);
79420
- }
79421
79596
  }
79422
- curNode.#methods.push({
79423
- [method]: {
79424
- handler,
79425
- possibleKeys: [...possibleKeys],
79426
- score: ++order
79427
- }
79428
- });
79597
+ curNode.#methods.push({ [method]: {
79598
+ handler,
79599
+ possibleKeys: [...possibleKeys],
79600
+ score: ++order
79601
+ } });
79429
79602
  }
79430
79603
  #pushHandlerSets(handlerSets, node, method, nodeParams, params) {
79431
79604
  for (let i = 0, len = node.#methods.length;i < len; i++) {
79432
79605
  const m = node.#methods[i];
79433
- const handlerSet = m[method] || m[METHOD_NAME_ALL];
79606
+ const handlerSet = m[method] || m["ALL"];
79434
79607
  if (handlerSet) {
79435
79608
  handlerSet.params = createNullObject();
79436
79609
  handlerSets.push(handlerSet);
79437
- for (let i2 = 0, len2 = handlerSet.possibleKeys.length;i2 < len2; i2++) {
79438
- const key = handlerSet.possibleKeys[i2];
79439
- handlerSet.params[key] = params?.[key] && !i2 ? params[key] : nodeParams[key] ?? params?.[key];
79610
+ for (let i = 0, len = handlerSet.possibleKeys.length;i < len; i++) {
79611
+ const key = handlerSet.possibleKeys[i];
79612
+ handlerSet.params[key] = params?.[key] && !i ? params[key] : nodeParams[key] ?? params?.[key];
79440
79613
  }
79441
79614
  }
79442
79615
  }
@@ -79444,8 +79617,7 @@ var Node2 = class _Node {
79444
79617
  search(method, path) {
79445
79618
  const handlerSets = [];
79446
79619
  this.#params = emptyParams;
79447
- const curNode = this;
79448
- let curNodes = [curNode];
79620
+ let curNodes = [this];
79449
79621
  const parts = splitPath(path);
79450
79622
  const curNodesQueue = [];
79451
79623
  const len = parts.length;
@@ -79460,13 +79632,11 @@ var Node2 = class _Node {
79460
79632
  if (nextNode) {
79461
79633
  nextNode.#params = node.#params;
79462
79634
  if (isLast) {
79463
- if (nextNode.#children["*"]) {
79635
+ if (nextNode.#children["*"])
79464
79636
  this.#pushHandlerSets(handlerSets, nextNode.#children["*"], method, node.#params);
79465
- }
79466
79637
  this.#pushHandlerSets(handlerSets, nextNode, method, node.#params);
79467
- } else {
79638
+ } else
79468
79639
  tempNodes.push(nextNode);
79469
- }
79470
79640
  }
79471
79641
  for (const child of node.#patterns) {
79472
79642
  const pattern = child.#pattern;
@@ -79482,9 +79652,8 @@ var Node2 = class _Node {
79482
79652
  continue;
79483
79653
  }
79484
79654
  const [, name, matcher] = pattern;
79485
- if (!part && matcher === true) {
79655
+ if (!part && matcher === true)
79486
79656
  continue;
79487
- }
79488
79657
  if (matcher !== true) {
79489
79658
  if (!partOffsets) {
79490
79659
  partOffsets = [];
@@ -79499,14 +79668,12 @@ var Node2 = class _Node {
79499
79668
  if (m) {
79500
79669
  params[name] = m[0];
79501
79670
  this.#pushHandlerSets(handlerSets, child, method, node.#params, params);
79502
- if (m[0].length === restPathString.length && child.#children["*"]) {
79671
+ if (m[0].length === restPathString.length && child.#children["*"])
79503
79672
  this.#pushHandlerSets(handlerSets, child.#children["*"], method, node.#params, params);
79504
- }
79505
79673
  for (const _ in child.#children) {
79506
79674
  child.#params = params;
79507
79675
  const componentCount = m[0].match(/\//g)?.length ?? 0;
79508
- const targetCurNodes = curNodesQueue[componentCount] ||= [];
79509
- targetCurNodes.push(child);
79676
+ (curNodesQueue[componentCount] ||= []).push(child);
79510
79677
  break;
79511
79678
  }
79512
79679
  continue;
@@ -79516,9 +79683,8 @@ var Node2 = class _Node {
79516
79683
  params[name] = part;
79517
79684
  if (isLast) {
79518
79685
  this.#pushHandlerSets(handlerSets, child, method, params, node.#params);
79519
- if (child.#children["*"]) {
79686
+ if (child.#children["*"])
79520
79687
  this.#pushHandlerSets(handlerSets, child.#children["*"], method, params, node.#params);
79521
- }
79522
79688
  } else {
79523
79689
  child.#params = params;
79524
79690
  tempNodes.push(child);
@@ -79529,71 +79695,63 @@ var Node2 = class _Node {
79529
79695
  const shifted = curNodesQueue.shift();
79530
79696
  curNodes = shifted ? tempNodes.concat(shifted) : tempNodes;
79531
79697
  }
79532
- if (handlerSets[1]) {
79698
+ if (handlerSets[1])
79533
79699
  handlerSets.sort((a, b) => {
79534
79700
  return a.score - b.score;
79535
79701
  });
79536
- }
79537
79702
  return [handlerSets.map(({ handler, params }) => [handler, params])];
79538
79703
  }
79539
79704
  };
79540
79705
 
79541
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/router/trie-router/router.js
79706
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/router/trie-router/router.js
79542
79707
  var TrieRouter = class {
79543
79708
  name = "TrieRouter";
79544
79709
  #node = new Node2;
79545
79710
  add(method, path, handler) {
79546
- for (const result of checkOptionalParameter(path) || [path]) {
79711
+ for (const result of checkOptionalParameter(path) || [path])
79547
79712
  this.#node.insert(method, result, handler);
79548
- }
79549
79713
  }
79550
79714
  match(method, path) {
79551
79715
  return this.#node.search(method, path);
79552
79716
  }
79553
79717
  };
79554
79718
 
79555
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/hono.js
79719
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/hono.js
79556
79720
  var Hono2 = class extends Hono {
79557
79721
  constructor(options = {}) {
79558
79722
  super(options);
79559
- this.router = options.router ?? new SmartRouter({
79560
- routers: [new RegExpRouter, new TrieRouter]
79561
- });
79723
+ this.router = options.router ?? new SmartRouter({ routers: [new RegExpRouter, new TrieRouter] });
79562
79724
  }
79563
79725
  };
79564
79726
 
79565
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/utils/color.js
79727
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/utils/color.js
79566
79728
  function getColorEnabled() {
79567
79729
  const { process: process2, Deno: Deno2 } = globalThis;
79568
- const isNoColor = typeof Deno2?.noColor === "boolean" ? Deno2.noColor : process2 !== undefined ? "NO_COLOR" in process2?.env : false;
79569
- return !isNoColor;
79730
+ return !(typeof Deno2?.noColor === "boolean" ? Deno2.noColor : process2 !== undefined ? "NO_COLOR" in process2?.env : false);
79570
79731
  }
79571
79732
  async function getColorEnabledAsync() {
79572
79733
  const { navigator: navigator2 } = globalThis;
79573
79734
  const cfWorkers = "cloudflare:workers";
79574
- const isNoColor = navigator2 !== undefined && navigator2.userAgent === "Cloudflare-Workers" ? await (async () => {
79735
+ return !(navigator2 !== undefined && navigator2.userAgent === "Cloudflare-Workers" ? await (async () => {
79575
79736
  try {
79576
79737
  return "NO_COLOR" in ((await import(cfWorkers)).env ?? {});
79577
79738
  } catch {
79578
79739
  return false;
79579
79740
  }
79580
- })() : !getColorEnabled();
79581
- return !isNoColor;
79741
+ })() : !getColorEnabled());
79582
79742
  }
79583
79743
 
79584
- // ../../node_modules/.bun/hono@4.13.7/node_modules/hono/dist/middleware/logger/index.js
79744
+ // ../../node_modules/.bun/hono@4.13.13/node_modules/hono/dist/middleware/logger/index.js
79585
79745
  var humanize = (times) => {
79586
79746
  const [delimiter, separator] = [",", "."];
79587
- const orderTimes = times.map((v) => v.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + delimiter));
79588
- return orderTimes.join(separator);
79747
+ return times.map((v) => v.replace(/(\d)(?=(\d\d\d)+(?!\d))/g, "$1" + delimiter)).join(separator);
79589
79748
  };
79590
79749
  var time2 = (start) => {
79591
79750
  const delta = Date.now() - start;
79592
79751
  return humanize([delta < 1000 ? delta + "ms" : Math.round(delta / 1000) + "s"]);
79593
79752
  };
79594
79753
  var colorStatus = async (status) => {
79595
- const colorEnabled = await getColorEnabledAsync();
79596
- if (colorEnabled) {
79754
+ if (await getColorEnabledAsync())
79597
79755
  switch (status / 100 | 0) {
79598
79756
  case 5:
79599
79757
  return `\x1B[31m${status}\x1B[0m`;
@@ -79604,15 +79762,13 @@ var colorStatus = async (status) => {
79604
79762
  case 2:
79605
79763
  return `\x1B[32m${status}\x1B[0m`;
79606
79764
  }
79607
- }
79608
79765
  return `${status}`;
79609
79766
  };
79610
79767
  async function log(fn, prefix, method, path, status = 0, elapsed) {
79611
- const out = prefix === "<--" ? `${prefix} ${method} ${path}` : `${prefix} ${method} ${path} ${await colorStatus(status)} ${elapsed}`;
79612
- fn(out);
79768
+ fn(prefix === "<--" ? `${prefix} ${method} ${path}` : `${prefix} ${method} ${path} ${await colorStatus(status)} ${elapsed}`);
79613
79769
  }
79614
79770
  var logger = (fn = console.log) => {
79615
- return async function logger2(c, next) {
79771
+ return async function logger(c, next) {
79616
79772
  const { method, url } = c.req;
79617
79773
  const path = url.slice(url.indexOf("/", 8));
79618
79774
  await log(fn, "<--", method, path);
@@ -79700,7 +79856,9 @@ function registerAuditUsageRoutes(app, ctx) {
79700
79856
  size_before: e.size_before ?? null,
79701
79857
  size_after: e.size_after ?? null,
79702
79858
  description: e.description ?? "",
79703
- created_at: e.created_at ?? ""
79859
+ created_at: e.created_at ?? "",
79860
+ metadata_before: e.metadata_before ?? null,
79861
+ metadata_after: e.metadata_after ?? null
79704
79862
  })));
79705
79863
  });
79706
79864
  app.get("/api/v1/usage-log", async (c) => {
@@ -80677,6 +80835,7 @@ function registerDocumentReadRoutes(app, ctx) {
80677
80835
  updated_at: meta ? meta.updated_at ?? null : null,
80678
80836
  deleted_at: meta ? meta.deleted_at ?? null : null,
80679
80837
  content_hash: meta ? meta.content_hash ?? null : null,
80838
+ metadata_revision: meta ? meta.metadata_revision ?? null : null,
80680
80839
  versions: versions.map((v) => ({
80681
80840
  version_id: v.version_id,
80682
80841
  version_number: v.version_number,
@@ -80797,11 +80956,13 @@ var EDIT_FIELDS = new Set([
80797
80956
  "project_ids",
80798
80957
  "metadata",
80799
80958
  "expected_content_hash",
80959
+ "expected_metadata_revision",
80800
80960
  "last_write_wins",
80801
80961
  "author",
80802
80962
  "requestor",
80803
80963
  "author_type"
80804
80964
  ]);
80965
+ var METADATA_TOKEN_DEPRECATION = "deprecated: metadata was replaced without expected_metadata_revision, as last-write-wins, " + "so a concurrent metadata change may have been overwritten. Send the metadata_revision " + "you read (GET /api/v1/documents/{id}); a future release will require it.";
80805
80966
  var TRASHED_MESSAGE = "This document was moved to the trash while you were editing. " + "Restore it from the Trash page first, then save again.";
80806
80967
  function conflictBody(currentHash) {
80807
80968
  const message = "This document changed while you were editing it (another writer saved a newer version). " + "Open it again in a new tab, merge your changes, and save from there.";
@@ -80813,6 +80974,10 @@ function conflictBody(currentHash) {
80813
80974
  ...currentHash ? { current_hash: currentHash } : {}
80814
80975
  };
80815
80976
  }
80977
+ function metadataConflictBody() {
80978
+ const message = "This document's metadata changed while you were editing it (another writer changed its tags). " + "Open it again in a new tab, reapply your changes, and save from there.";
80979
+ return { success: false, error: "metadata_conflict", message, detail: message };
80980
+ }
80816
80981
  function registerDocumentWriteRoutes(app, ctx) {
80817
80982
  app.post("/api/v1/documents/:document_id/edit", async (c) => {
80818
80983
  const documentId = c.req.param("document_id");
@@ -80863,6 +81028,11 @@ function registerDocumentWriteRoutes(app, ctx) {
80863
81028
  if (expectedHash !== null && !lastWriteWins && currentHash !== null && expectedHash !== currentHash) {
80864
81029
  return c.json(conflictBody(currentHash), 409);
80865
81030
  }
81031
+ const metadataCarried = body.metadata != null;
81032
+ const sentRevision = typeof body.expected_metadata_revision === "number" && Number.isInteger(body.expected_metadata_revision) ? body.expected_metadata_revision : null;
81033
+ const metadataTokenMissing = metadataCarried && sentRevision === null && !lastWriteWins;
81034
+ const warnings = metadataTokenMissing ? [METADATA_TOKEN_DEPRECATION] : undefined;
81035
+ const expectedMetadataRevision = sentRevision ?? (doc.metadata_revision ?? null);
80866
81036
  const proposedHash = content.trim() ? contentHash(content) : null;
80867
81037
  const contentChanged = proposedHash !== null && currentHash !== null && proposedHash !== currentHash;
80868
81038
  if (contentChanged) {
@@ -80884,17 +81054,22 @@ function registerDocumentWriteRoutes(app, ctx) {
80884
81054
  title: title || doc.title,
80885
81055
  source: "manual",
80886
81056
  projectIds: Array.isArray(body.project_ids) ? body.project_ids : undefined,
80887
- metadata: body.metadata != null ? metadata : undefined,
81057
+ metadata: metadataCarried ? metadata : undefined,
81058
+ replaceMetadata: metadataCarried && doc.metadata_revision != null,
81059
+ expectedMetadataRevision: metadataCarried ? expectedMetadataRevision : null,
80888
81060
  author: who.identity.author,
80889
81061
  authorType: who.identity.authorType,
80890
81062
  expectedContentHash: expectedHash,
80891
81063
  lastWriteWins
80892
81064
  });
80893
- return c.json({ success: true, reindexed: result.reindexed });
81065
+ return c.json({ success: true, reindexed: result.reindexed, ...warnings ? { warnings } : {} });
80894
81066
  } catch (err) {
80895
81067
  if (err instanceof ConcurrencyConflictError) {
80896
81068
  return c.json(conflictBody(err.currentHash), 409);
80897
81069
  }
81070
+ if (err instanceof MetadataConcurrencyError) {
81071
+ return err.tokenRequired ? c.json({ success: false, error: "expected_metadata_revision required", message: err.message, detail: err.message }, 400) : c.json(metadataConflictBody(), 409);
81072
+ }
80898
81073
  if (err instanceof ConcurrencyTokenRequiredError) {
80899
81074
  return c.json({
80900
81075
  success: false,
@@ -80924,13 +81099,15 @@ function registerDocumentWriteRoutes(app, ctx) {
80924
81099
  const facets = await updateDocumentFacets(ctx.supabase, {
80925
81100
  documentId,
80926
81101
  title: body.title !== undefined && title !== doc.title ? title : undefined,
80927
- metadata: body.metadata != null ? metadata : undefined,
81102
+ metadata: metadataCarried ? metadata : undefined,
81103
+ expectedMetadataRevision: metadataTokenMissing ? null : expectedMetadataRevision,
81104
+ metadataLastWriteWins: lastWriteWins || metadataTokenMissing,
80928
81105
  projectIds: Array.isArray(body.project_ids) ? projectIds : undefined,
80929
81106
  author: who.identity.author,
80930
81107
  authorType: who.identity.authorType,
80931
81108
  accessPath: who.identity.accessPath
80932
81109
  });
80933
- return c.json({ success: true, reindexed: false, ...facets });
81110
+ return c.json({ success: true, reindexed: false, ...facets, ...warnings ? { warnings } : {} });
80934
81111
  } catch (err) {
80935
81112
  const cause = err instanceof FacetUpdateError ? err.cause : err;
80936
81113
  const msg = err instanceof Error ? err.message : String(err);
@@ -80940,6 +81117,9 @@ function registerDocumentWriteRoutes(app, ctx) {
80940
81117
  if (cause instanceof FacetValidationError) {
80941
81118
  return c.json({ success: false, error: msg, detail: msg }, 400);
80942
81119
  }
81120
+ if (cause instanceof MetadataConcurrencyError) {
81121
+ return cause.tokenRequired ? c.json({ success: false, error: "expected_metadata_revision required", message: msg, detail: msg }, 400) : c.json(metadataConflictBody(), 409);
81122
+ }
80943
81123
  return c.json({ success: false, error: msg, detail: msg }, 500);
80944
81124
  }
80945
81125
  });
@@ -81112,6 +81292,9 @@ function ingestFailure(err) {
81112
81292
  if (err instanceof ConcurrencyTokenRequiredError) {
81113
81293
  return [notReady(err.message), 400];
81114
81294
  }
81295
+ if (err instanceof MetadataConcurrencyError) {
81296
+ return [notReady(err.message), err.tokenRequired ? 400 : 409];
81297
+ }
81115
81298
  const msg = err instanceof Error ? err.message : String(err);
81116
81299
  if (/^Document (.* )?not found/.test(msg))
81117
81300
  return [notReady(msg), 404];
@@ -81126,6 +81309,7 @@ var INGEST_FIELDS = new Set([
81126
81309
  "content",
81127
81310
  "document_id",
81128
81311
  "expected_content_hash",
81312
+ "expected_metadata_revision",
81129
81313
  "last_write_wins",
81130
81314
  "update_existing",
81131
81315
  "update_if_exists",
@@ -81156,6 +81340,9 @@ function ingestShapeError(body) {
81156
81340
  if (body[k] != null && typeof body[k] !== "boolean")
81157
81341
  return `${k} must be a boolean.`;
81158
81342
  }
81343
+ if (body.expected_metadata_revision != null && !Number.isInteger(body.expected_metadata_revision)) {
81344
+ return "expected_metadata_revision must be an integer.";
81345
+ }
81159
81346
  return null;
81160
81347
  }
81161
81348
  function registerIngestRoutes(app, ctx) {
@@ -81203,7 +81390,8 @@ function registerIngestRoutes(app, ctx) {
81203
81390
  author: who.identity.author,
81204
81391
  authorType: who.identity.authorType,
81205
81392
  expectedContentHash: typeof body.expected_content_hash === "string" && body.expected_content_hash.trim() !== "" ? body.expected_content_hash.trim() : null,
81206
- lastWriteWins: body.last_write_wins === true
81393
+ lastWriteWins: body.last_write_wins === true,
81394
+ expectedMetadataRevision: body.expected_metadata_revision ?? null
81207
81395
  });
81208
81396
  const skipped = result.action === "skipped";
81209
81397
  const resp = {