papiers 0.1.2 → 0.1.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -268,7 +268,7 @@ function resolveCliConfig(params) {
268
268
  const token = firstNonEmpty([params.env[TOKEN_ENV_VAR]]) ?? null;
269
269
  return {
270
270
  apiBaseUrl: normalizedBaseUrl,
271
- apiBaseUrlSource: params.apiBaseUrlOverride ? "--api-base-url" : params.env[API_BASE_URL_ENV_VAR]?.trim() ? API_BASE_URL_ENV_VAR : "default",
271
+ apiBaseUrlSource: params.apiBaseUrlOverride ? "--api-url" : params.env[API_BASE_URL_ENV_VAR]?.trim() ? API_BASE_URL_ENV_VAR : "default",
272
272
  token,
273
273
  tokenSource: token ? TOKEN_ENV_VAR : null,
274
274
  tokenPreview: token ? redactPapiersToken(token) : null
@@ -412,23 +412,41 @@ function isNodeError(error) {
412
412
  return Boolean(error && typeof error === "object" && "code" in error);
413
413
  }
414
414
 
415
+ // ../../src/shared/papiers-resource-locators.ts
416
+ var UUID_PATTERN = "[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}";
417
+ var PAPIERS_RESOURCE_LOCATOR_PATTERN_SOURCE = `papiers://(?:note|highlight|item|conversation|artifact)/${UUID_PATTERN}`;
418
+ var RESOURCE_LOCATOR_PATTERN = new RegExp(
419
+ `^papiers://(note|highlight|item|conversation|artifact)/(${UUID_PATTERN})$`,
420
+ "u"
421
+ );
422
+ var UUID_REGEX = new RegExp(`^${UUID_PATTERN}$`, "iu");
423
+ var READ_KIND_BY_LOCATOR_KIND = {
424
+ note: "note",
425
+ highlight: "highlight",
426
+ item: "document",
427
+ conversation: "conversation",
428
+ artifact: "artifact"
429
+ };
430
+ function parsePapiersResourceLocator(value) {
431
+ const match = RESOURCE_LOCATOR_PATTERN.exec(value);
432
+ if (!match) return null;
433
+ const kind = match[1];
434
+ return {
435
+ value,
436
+ kind,
437
+ readKind: READ_KIND_BY_LOCATOR_KIND[kind],
438
+ internalId: match[2].toLowerCase()
439
+ };
440
+ }
441
+
415
442
  // src/document-contract.ts
416
443
  var PUBLIC_KINDS = [
417
444
  "document",
418
445
  "passage",
419
446
  "highlight",
420
447
  "conversation",
421
- "equation"
422
- ];
423
- var EXPECTED_DOCUMENT_QUERY_IN = [
424
- "title",
425
- "metadata"
426
- ];
427
- var EXPECTED_CONTENT_QUERY_IN = [
428
- "content"
429
- ];
430
- var EXPECTED_EQUATION_QUERY_IN = [
431
- "latex"
448
+ "equation",
449
+ "note"
432
450
  ];
433
451
  var EXPECTED_DOCUMENT_READ_FORMATS = [
434
452
  "metadata",
@@ -450,6 +468,10 @@ var EXPECTED_CONVERSATION_READ_FORMATS = [
450
468
  var EXPECTED_EQUATION_READ_FORMATS = [
451
469
  "metadata"
452
470
  ];
471
+ var EXPECTED_NOTE_READ_FORMATS = [
472
+ "markdown",
473
+ "plain"
474
+ ];
453
475
  var DOCUMENT_READ_REQUEST_FORMATS = [
454
476
  ...EXPECTED_DOCUMENT_READ_FORMATS,
455
477
  "auto"
@@ -478,10 +500,13 @@ var EXPECTED_DOCUMENT_FILTERS = [
478
500
  "engaged",
479
501
  "lastEngagedAt"
480
502
  ];
503
+ var EXPECTED_CONVERSATION_FILTERS = [
504
+ "lastEngagedAt"
505
+ ];
481
506
  var EXPECTED_DOCUMENT_FILTER_OPERATORS = {
482
507
  id: ["eq", "in"],
483
508
  title: ["eq", "contains", "prefix", "in"],
484
- author: ["eq"],
509
+ author: ["eq", "contains"],
485
510
  venue: ["eq", "contains", "prefix", "in"],
486
511
  publishedDate: ["eq", "gte", "gt", "lte", "lt", "between"],
487
512
  publishedYear: ["eq", "gte", "gt", "lte", "lt", "between", "in"],
@@ -513,6 +538,7 @@ var PASSAGE_PUBLIC_ID_PATTERN = /^pas_[a-f0-9]{32}$/u;
513
538
  var HIGHLIGHT_PUBLIC_ID_PATTERN = /^hl_[a-f0-9]{32}$/u;
514
539
  var CONVERSATION_PUBLIC_ID_PATTERN = /^cnv_[a-f0-9]{32}$/u;
515
540
  var EQUATION_PUBLIC_ID_PATTERN = /^eq_[a-f0-9]{32}$/u;
541
+ var NOTE_PUBLIC_ID_PATTERN = /^nte_[a-f0-9]{32}$/u;
516
542
  var SEARCH_RANKING_ID_PATTERN = /^srch_[a-f0-9]{32}$/u;
517
543
  function isSearchRankingId(value) {
518
544
  return SEARCH_RANKING_ID_PATTERN.test(value);
@@ -532,6 +558,9 @@ function isConversationPublicId(value) {
532
558
  function isEquationPublicId(value) {
533
559
  return EQUATION_PUBLIC_ID_PATTERN.test(value);
534
560
  }
561
+ function isNotePublicId(value) {
562
+ return NOTE_PUBLIC_ID_PATTERN.test(value);
563
+ }
535
564
  function publicReadKindForId(value) {
536
565
  if (isDocumentPublicId(value)) {
537
566
  return "document";
@@ -548,8 +577,22 @@ function publicReadKindForId(value) {
548
577
  if (isEquationPublicId(value)) {
549
578
  return "equation";
550
579
  }
580
+ if (isNotePublicId(value)) {
581
+ return "note";
582
+ }
551
583
  return null;
552
584
  }
585
+ function readKindForIdentifier(value) {
586
+ const publicKind = publicReadKindForId(value);
587
+ if (publicKind) {
588
+ return publicKind;
589
+ }
590
+ const locatorKind = parsePapiersResourceLocator(value)?.readKind;
591
+ return locatorKind === "artifact" ? null : locatorKind ?? null;
592
+ }
593
+ function isDocumentReadIdentifier(value) {
594
+ return isDocumentPublicId(value) || parsePapiersResourceLocator(value)?.kind === "item";
595
+ }
553
596
  function isPublicIdForKind(value, kind) {
554
597
  return publicReadKindForId(value) === kind;
555
598
  }
@@ -598,6 +641,27 @@ var PAPIERS_SEARCH_QUALITY_SIGNALS = [
598
641
  "low",
599
642
  "empty"
600
643
  ];
644
+ var PAPIERS_SEARCH_RESULT_KINDS = [
645
+ "document",
646
+ "conversation",
647
+ "note"
648
+ ];
649
+ var PAPIERS_SEARCH_EVIDENCE_KINDS = [
650
+ "passage",
651
+ "highlight",
652
+ "equation"
653
+ ];
654
+ var PAPIERS_SEARCH_MATCH_REASONS = [
655
+ "title",
656
+ "doi",
657
+ "arxivId",
658
+ "pmid",
659
+ "passage",
660
+ "highlight",
661
+ "equation",
662
+ "conversation",
663
+ "note"
664
+ ];
601
665
  var PAPIERS_PUBLIC_NOT_READY_REASONS_BY_FEATURE = {
602
666
  document_content_read: ["explicit_enablement_required"],
603
667
  non_document_kinds: ["public_id_projection_not_ready"],
@@ -613,7 +677,10 @@ var PAPIERS_PUBLIC_NOT_READY_REASONS_BY_FEATURE = {
613
677
  read_section_scopes: ["section_scoped_read_not_ready"],
614
678
  read_context: ["read_context_not_ready"],
615
679
  ranked_search_cursors: ["search_sessions_not_ready"],
616
- search_results_browse: ["content_search_not_ready"]
680
+ search_results_browse: [
681
+ "content_search_not_ready",
682
+ "search_sessions_not_ready"
683
+ ]
617
684
  };
618
685
  var PAPIERS_PUBLIC_ERROR_CODES = [
619
686
  "api_disabled",
@@ -636,6 +703,8 @@ var PAPIERS_PUBLIC_ERROR_CODES = [
636
703
  "invalid_request",
637
704
  "not_found",
638
705
  "source_superseded",
706
+ "content_limit_exceeded",
707
+ "request_limit_exceeded",
639
708
  "rate_limited",
640
709
  "internal_error"
641
710
  ];
@@ -746,7 +815,7 @@ var SEARCH_RESPONSE_KEYS = /* @__PURE__ */ new Set([
746
815
  "requestId",
747
816
  "query",
748
817
  "quality",
749
- "items",
818
+ "results",
750
819
  "page",
751
820
  "ranking",
752
821
  "warnings"
@@ -760,6 +829,9 @@ var SEARCH_RANKING_KEYS = /* @__PURE__ */ new Set([
760
829
  ]);
761
830
  var SEARCH_QUALITY_KEYS = /* @__PURE__ */ new Set(["signal", "suggestions"]);
762
831
  var SEARCH_QUALITY_SIGNALS = new Set(PAPIERS_SEARCH_QUALITY_SIGNALS);
832
+ var SEARCH_RESULT_KINDS = new Set(PAPIERS_SEARCH_RESULT_KINDS);
833
+ var SEARCH_EVIDENCE_KINDS = new Set(PAPIERS_SEARCH_EVIDENCE_KINDS);
834
+ var SEARCH_MATCH_REASONS = new Set(PAPIERS_SEARCH_MATCH_REASONS);
763
835
  var PUBLIC_WARNING_KEYS = /* @__PURE__ */ new Set(["code", "message"]);
764
836
  var DOCUMENT_SEARCH_WARNING_CODE = "metadata_only_search";
765
837
  var DOCUMENT_SEARCH_WARNING_MESSAGE = "Public document search currently searches catalog title and metadata, not document body content.";
@@ -771,6 +843,7 @@ var RANKED_SEARCH_CURSOR_WARNING_MESSAGES = /* @__PURE__ */ new Set([
771
843
  var DOCUMENT_READ_TRUNCATED_WARNING_CODE = "read_truncated";
772
844
  var DOCUMENT_READ_TRUNCATED_WARNING_MESSAGE = "Document content was truncated by public read limits.";
773
845
  var PASSAGE_READ_TRUNCATED_WARNING_MESSAGE = "Passage content was truncated by public read limits.";
846
+ var NOTE_READ_TRUNCATED_WARNING_MESSAGE = "Note content was truncated by public read limits.";
774
847
  var READ_RESPONSE_KEYS = /* @__PURE__ */ new Set([
775
848
  "schemaVersion",
776
849
  "requestId",
@@ -821,6 +894,37 @@ var SEARCH_DOCUMENT_ITEM_KEYS = /* @__PURE__ */ new Set([
821
894
  "metadata",
822
895
  "updatedAt"
823
896
  ]);
897
+ var SEARCH_RESULT_BASE_KEYS = [
898
+ "id",
899
+ "kind",
900
+ "rank",
901
+ "score",
902
+ "matchedBy",
903
+ "title"
904
+ ];
905
+ var SEARCH_DOCUMENT_RESULT_KEYS = /* @__PURE__ */ new Set([
906
+ ...SEARCH_RESULT_BASE_KEYS,
907
+ "source",
908
+ "metadata",
909
+ "evidence",
910
+ "evidenceTotal"
911
+ ]);
912
+ var SEARCH_CONVERSATION_RESULT_KEYS = /* @__PURE__ */ new Set([
913
+ ...SEARCH_RESULT_BASE_KEYS,
914
+ "snippet"
915
+ ]);
916
+ var SEARCH_NOTE_RESULT_KEYS = /* @__PURE__ */ new Set([
917
+ ...SEARCH_RESULT_BASE_KEYS,
918
+ "snippet",
919
+ "metadata"
920
+ ]);
921
+ var SEARCH_EVIDENCE_KEYS = /* @__PURE__ */ new Set([
922
+ "kind",
923
+ "id",
924
+ "rank",
925
+ "snippet",
926
+ "score"
927
+ ]);
824
928
  var READ_SUCCESS_DOCUMENT_ITEM_KEYS = /* @__PURE__ */ new Set([
825
929
  "id",
826
930
  "kind",
@@ -866,6 +970,19 @@ var READ_SUCCESS_EQUATION_ITEM_KEYS = /* @__PURE__ */ new Set([
866
970
  "format",
867
971
  "metadata"
868
972
  ]);
973
+ var READ_SUCCESS_NOTE_ITEM_KEYS = /* @__PURE__ */ new Set([
974
+ "id",
975
+ "kind",
976
+ "ok",
977
+ "format",
978
+ "content",
979
+ "truncated",
980
+ "charCount",
981
+ "tokenEstimate",
982
+ "metadata",
983
+ "nextCursor",
984
+ "warnings"
985
+ ]);
869
986
  var READ_ERROR_DOCUMENT_ITEM_KEYS = /* @__PURE__ */ new Set(["id", "kind", "ok", "error"]);
870
987
  var READ_ITEM_ERROR_KEYS = /* @__PURE__ */ new Set(["code", "message", "retryable"]);
871
988
  var HIGHLIGHT_READ_METADATA_KEYS = /* @__PURE__ */ new Set([
@@ -875,6 +992,8 @@ var HIGHLIGHT_READ_METADATA_KEYS = /* @__PURE__ */ new Set([
875
992
  "anchorType",
876
993
  "annotationText",
877
994
  "quoteContext",
995
+ "quotePrefix",
996
+ "quoteSuffix",
878
997
  "createdAt",
879
998
  "updatedAt"
880
999
  ]);
@@ -896,6 +1015,14 @@ var EQUATION_READ_METADATA_KEYS = /* @__PURE__ */ new Set([
896
1015
  "nodeType",
897
1016
  "updatedAt"
898
1017
  ]);
1018
+ var NOTE_READ_METADATA_KEYS = /* @__PURE__ */ new Set([
1019
+ "documentId",
1020
+ "attachedTitle",
1021
+ "editedAt",
1022
+ "createdAt",
1023
+ "updatedAt",
1024
+ "title"
1025
+ ]);
899
1026
  var READ_LOCATIONS_KEYS = /* @__PURE__ */ new Set(["pages", "scope", "truncated"]);
900
1027
  var READ_LOCATION_PAGE_KEYS = /* @__PURE__ */ new Set(["page", "sections"]);
901
1028
  var READ_LOCATION_SCOPE_KEYS = /* @__PURE__ */ new Set(["pages"]);
@@ -927,9 +1054,16 @@ var PASSAGE_READ_NOT_FOUND_MESSAGE = "Passage is not available.";
927
1054
  var HIGHLIGHT_READ_NOT_FOUND_MESSAGE = "Highlight is not available.";
928
1055
  var CONVERSATION_READ_NOT_FOUND_MESSAGE = "Conversation is not available.";
929
1056
  var EQUATION_READ_NOT_FOUND_MESSAGE = "Equation is not available.";
1057
+ var NOTE_READ_NOT_FOUND_MESSAGE = "Note is not available.";
930
1058
  var DOCUMENT_READ_SOURCE_SUPERSEDED_CODE = "source_superseded";
1059
+ var CONVERSATION_READ_CONTENT_LIMIT_CODE = "content_limit_exceeded";
1060
+ var CONVERSATION_READ_REQUEST_LIMIT_CODE = "request_limit_exceeded";
931
1061
  var DOCUMENT_READ_SOURCE_SUPERSEDED_MESSAGE = "Document content is not available for the current source.";
1062
+ var NOTE_READ_SOURCE_SUPERSEDED_MESSAGE = "Note content changed since this cursor was issued.";
932
1063
  var PASSAGE_READ_CONTENT_UNAVAILABLE_MESSAGE = "Passage content is not available.";
1064
+ var CONVERSATION_READ_SOURCE_SUPERSEDED_MESSAGE = "Conversation source is no longer available. Search again for a current result.";
1065
+ var CONVERSATION_READ_CONTENT_LIMIT_MESSAGE = "Conversation content exceeds the public read limit.";
1066
+ var CONVERSATION_READ_REQUEST_LIMIT_MESSAGE = "Conversation read batch is too large. Retry this item in a smaller batch.";
933
1067
  var PUBLIC_CURSOR_BASE64URL_PATTERN = /^[A-Za-z0-9_-]{1,4096}$/u;
934
1068
  var PUBLIC_CURSOR_SIGNATURE_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
935
1069
  var PUBLIC_CURSOR_HASH_PATTERN = /^[A-Za-z0-9_-]{43}$/u;
@@ -989,6 +1123,8 @@ var INTERNAL_RESPONSE_KEYS = /* @__PURE__ */ new Set([
989
1123
  "latexTokens",
990
1124
  "library_item_id",
991
1125
  "libraryItemId",
1126
+ "note_id",
1127
+ "noteId",
992
1128
  "actor_id",
993
1129
  "actorId",
994
1130
  "owner_user_id",
@@ -1012,7 +1148,7 @@ var INTERNAL_RESPONSE_KEYS = /* @__PURE__ */ new Set([
1012
1148
  "last_used_ip_hash",
1013
1149
  "lastUsedIpHash"
1014
1150
  ]);
1015
- var UNSAFE_PUBLIC_EXCERPT_MARKER_PATTERN = /\b(?:artifact_id|artifactId|astNodeId|atom_id|atomId|branchKey|contentHash|conversation_id|conversationId|current_artifact_id|currentArtifactId|current_internal_target_json|currentInternalTargetJson|internal_locator|internalLocator|latexTokens|library_item_id|libraryItemId|actor_id|actorId|owner_user_id|ownerUserId|secret_hash|secretHash|source_id|sourceId|source_url|sourceUrl|source_locator|sourceLocator|source_locator_hash|sourceLocatorHash|source_locator_json|sourceLocatorJson|projectionHash|search_row_id|searchRowId|last_used_ip_hash|lastUsedIpHash)\b/u;
1151
+ var UNSAFE_PUBLIC_EXCERPT_MARKER_PATTERN = /\b(?:artifact_id|artifactId|astNodeId|atom_id|atomId|branchKey|contentHash|conversation_id|conversationId|current_artifact_id|currentArtifactId|current_internal_target_json|currentInternalTargetJson|internal_locator|internalLocator|latexTokens|library_item_id|libraryItemId|note_id|noteId|actor_id|actorId|owner_user_id|ownerUserId|secret_hash|secretHash|source_id|sourceId|source_url|sourceUrl|source_locator|sourceLocator|source_locator_hash|sourceLocatorHash|source_locator_json|sourceLocatorJson|projectionHash|search_row_id|searchRowId|last_used_ip_hash|lastUsedIpHash)\b/u;
1016
1152
  var UNSAFE_PUBLIC_EXCERPT_URL_PATTERN = /\bhttps?:\/\/\S+[?&](?:access_token|key|secret|sig|signature|token)=/iu;
1017
1153
  var UNSAFE_PUBLIC_EXCERPT_SIGNED_URL_PATTERN = /\bsigned[_-]?url\b/iu;
1018
1154
  var CLI_DOCUMENT_READ_FORMAT_SET = new Set(
@@ -1050,17 +1186,12 @@ function assertPapiersSearchResponse(label, body, expectedQuery, options = {}) {
1050
1186
  `${label} did not echo the requested query.`
1051
1187
  );
1052
1188
  }
1053
- const itemCount = validateListSearchItems(label, value.items, {
1054
- expectedKind: options.expectedKind,
1055
- expectedKinds: options.expectedKinds,
1056
- mode: "search",
1057
- requireRank: true
1058
- });
1059
- const searchItems = value.items;
1060
- for (const [index, item] of searchItems.entries()) {
1061
- if (item.rank !== index + 1) {
1189
+ const itemCount = validateSearchResults(label, value.results, options);
1190
+ const searchResults = value.results;
1191
+ for (const [index, result] of searchResults.entries()) {
1192
+ if (result.rank !== index + 1) {
1062
1193
  throw new PapiersCliResponseValidationError(
1063
- `${label} item ${index} rank was not contiguous from 1.`
1194
+ `${label} result ${index} rank was not contiguous from 1.`
1064
1195
  );
1065
1196
  }
1066
1197
  }
@@ -1080,10 +1211,7 @@ function assertPapiersSearchResultsResponse(label, body, request) {
1080
1211
  rejectUnexpectedKeys(label, value, SEARCH_RESPONSE_KEYS);
1081
1212
  assertPublicRequestId(`${label} requestId`, value.requestId);
1082
1213
  assertNonEmptyString(`${label} query`, value.query);
1083
- const itemCount = validateListSearchItems(label, value.items, {
1084
- mode: "search",
1085
- requireRank: true
1086
- });
1214
+ const itemCount = validateSearchResults(label, value.results);
1087
1215
  validatePage(label, value.page, itemCount, { cursor: "none" });
1088
1216
  validateOptionalSearchQuality(`${label} quality`, value.quality);
1089
1217
  if (value.ranking === void 0) {
@@ -1118,12 +1246,12 @@ function assertPapiersSearchResultsResponse(label, body, request) {
1118
1246
  `${label} returned more items than the consumed ranking window.`
1119
1247
  );
1120
1248
  }
1121
- const items = value.items;
1249
+ const items = value.results;
1122
1250
  let previousRank = expectedOffset;
1123
1251
  for (const [index, item] of items.entries()) {
1124
1252
  if (typeof item.rank !== "number" || item.rank <= previousRank || item.rank > consumedBound) {
1125
1253
  throw new PapiersCliResponseValidationError(
1126
- `${label} item ${index} rank was not strictly increasing within the frozen ranking window.`
1254
+ `${label} result ${index} rank was not strictly increasing within the frozen ranking window.`
1127
1255
  );
1128
1256
  }
1129
1257
  previousRank = item.rank;
@@ -1243,10 +1371,10 @@ function validatePapiersReadResponse(label, body, options = {}) {
1243
1371
  const expectedInclude = options.expectedInclude ? new Set(options.expectedInclude) : void 0;
1244
1372
  for (const [index, rawItem] of items.entries()) {
1245
1373
  const item = assertRecord(`${label} item ${index}`, rawItem);
1246
- const id = assertReadPublicId(`${label} item ${index} id`, item.id);
1374
+ const id = assertReadIdentifier(`${label} item ${index} id`, item.id);
1247
1375
  if (options.expectedIds !== void 0 && id !== options.expectedIds[index]) {
1248
1376
  throw new PapiersCliResponseValidationError(
1249
- `${label} did not echo the requested public ids in order.`
1377
+ `${label} did not echo the requested identifiers in order.`
1250
1378
  );
1251
1379
  }
1252
1380
  const kind = validateReadItemKind(
@@ -1332,6 +1460,190 @@ function validateTokenSummary(label, rawToken) {
1332
1460
  validateOptionalTimestamp(`${label} revokedAt`, token.revokedAt);
1333
1461
  validateOptionalTimestamp(`${label} lastUsedAt`, token.lastUsedAt);
1334
1462
  }
1463
+ function validateSearchResults(label, results, options = {}) {
1464
+ const values = assertArray(`${label} results`, results);
1465
+ for (const [index, rawResult] of values.entries()) {
1466
+ const result = assertRecord(`${label} result ${index}`, rawResult);
1467
+ rejectUnexpectedContent(`${label} result ${index}`, result);
1468
+ const kind = validateSearchResultKind(
1469
+ `${label} result ${index} kind`,
1470
+ result.kind,
1471
+ result.id
1472
+ );
1473
+ if (options.expectedKind !== void 0 && kind !== options.expectedKind) {
1474
+ throw new PapiersCliResponseValidationError(
1475
+ `${label} result ${index} kind did not match the requested kind.`
1476
+ );
1477
+ }
1478
+ if (options.expectedKinds !== void 0 && !options.expectedKinds.has(kind)) {
1479
+ throw new PapiersCliResponseValidationError(
1480
+ `${label} result ${index} kind was not one of the requested kinds.`
1481
+ );
1482
+ }
1483
+ switch (kind) {
1484
+ case "document":
1485
+ validateSearchDocumentResult(`${label} result ${index}`, result);
1486
+ break;
1487
+ case "conversation":
1488
+ validateSearchConversationResult(`${label} result ${index}`, result);
1489
+ break;
1490
+ case "note":
1491
+ validateSearchNoteResult(`${label} result ${index}`, result);
1492
+ break;
1493
+ }
1494
+ }
1495
+ return values.length;
1496
+ }
1497
+ function validateSearchResultKind(label, value, id) {
1498
+ if (typeof id !== "string") {
1499
+ throw new PapiersCliResponseValidationError(`${label} id was invalid.`);
1500
+ }
1501
+ if (!SEARCH_RESULT_KINDS.has(String(value))) {
1502
+ throw new PapiersCliResponseValidationError(
1503
+ `${label} was not a top-level search result kind.`
1504
+ );
1505
+ }
1506
+ if (value === "document") {
1507
+ assertDocId(`${label} id`, id);
1508
+ return "document";
1509
+ }
1510
+ if (value === "conversation" && isPublicIdForKind(id, "conversation")) {
1511
+ return "conversation";
1512
+ }
1513
+ if (value === "note" && isPublicIdForKind(id, "note")) {
1514
+ return "note";
1515
+ }
1516
+ throw new PapiersCliResponseValidationError(
1517
+ `${label} did not match the public id kind.`
1518
+ );
1519
+ }
1520
+ function validateSearchDocumentResult(label, result) {
1521
+ rejectUnexpectedKeys(label, result, SEARCH_DOCUMENT_RESULT_KEYS);
1522
+ assertDocId(`${label} id`, result.id);
1523
+ assertPositiveInteger(`${label} rank`, result.rank);
1524
+ validateSearchScore(`${label} score`, result.score);
1525
+ validateSearchMatchedBy(`${label} matchedBy`, result.matchedBy, {
1526
+ allow: /* @__PURE__ */ new Set([
1527
+ "title",
1528
+ "doi",
1529
+ "arxivId",
1530
+ "pmid",
1531
+ "passage",
1532
+ "highlight",
1533
+ "equation"
1534
+ ])
1535
+ });
1536
+ validateOptionalNullableString(`${label} title`, result.title);
1537
+ validateOptionalDocumentCatalogMetadata(`${label} metadata`, result.metadata);
1538
+ validateOptionalMetadataReadSource(`${label} source`, result.source);
1539
+ const evidenceCount = validateSearchEvidenceList(
1540
+ `${label} evidence`,
1541
+ result.evidence
1542
+ );
1543
+ const evidenceTotal = assertNonNegativeInteger(
1544
+ `${label} evidenceTotal`,
1545
+ result.evidenceTotal
1546
+ );
1547
+ if (evidenceTotal < evidenceCount) {
1548
+ throw new PapiersCliResponseValidationError(
1549
+ `${label} evidenceTotal was smaller than evidence.length.`
1550
+ );
1551
+ }
1552
+ }
1553
+ function validateSearchConversationResult(label, result) {
1554
+ rejectUnexpectedKeys(label, result, SEARCH_CONVERSATION_RESULT_KEYS);
1555
+ const id = assertNonEmptyString(`${label} id`, result.id);
1556
+ if (!isPublicIdForKind(id, "conversation")) {
1557
+ throw new PapiersCliResponseValidationError(
1558
+ `${label} id was not a cnv_* public id.`
1559
+ );
1560
+ }
1561
+ assertPositiveInteger(`${label} rank`, result.rank);
1562
+ validateSearchScore(`${label} score`, result.score);
1563
+ validateSearchMatchedBy(`${label} matchedBy`, result.matchedBy, {
1564
+ allow: /* @__PURE__ */ new Set(["conversation"])
1565
+ });
1566
+ validateOptionalNullableString(`${label} title`, result.title);
1567
+ validateOptionalPublicExcerpt(`${label} snippet`, result.snippet);
1568
+ }
1569
+ function validateSearchNoteResult(label, result) {
1570
+ rejectUnexpectedKeys(label, result, SEARCH_NOTE_RESULT_KEYS);
1571
+ const id = assertNonEmptyString(`${label} id`, result.id);
1572
+ if (!isPublicIdForKind(id, "note")) {
1573
+ throw new PapiersCliResponseValidationError(
1574
+ `${label} id was not an nte_* public id.`
1575
+ );
1576
+ }
1577
+ assertPositiveInteger(`${label} rank`, result.rank);
1578
+ validateSearchScore(`${label} score`, result.score);
1579
+ validateSearchMatchedBy(`${label} matchedBy`, result.matchedBy, {
1580
+ allow: /* @__PURE__ */ new Set(["note"])
1581
+ });
1582
+ validateOptionalNullableString(`${label} title`, result.title);
1583
+ validateOptionalPublicExcerpt(`${label} snippet`, result.snippet);
1584
+ validateNoteMetadata(`${label} metadata`, result.metadata, assertDocId);
1585
+ }
1586
+ function validateSearchEvidenceList(label, value) {
1587
+ const evidence = assertArray(label, value);
1588
+ for (const [index, rawEvidence] of evidence.entries()) {
1589
+ const item = assertRecord(`${label} ${index}`, rawEvidence);
1590
+ rejectUnexpectedKeys(`${label} ${index}`, item, SEARCH_EVIDENCE_KEYS);
1591
+ validateSearchEvidenceKind(`${label} ${index} kind`, item.kind, item.id);
1592
+ assertPositiveInteger(`${label} ${index} rank`, item.rank);
1593
+ if (item.rank !== index + 1) {
1594
+ throw new PapiersCliResponseValidationError(
1595
+ `${label} ${index} rank was not contiguous from 1.`
1596
+ );
1597
+ }
1598
+ validateOptionalPublicExcerpt(`${label} ${index} snippet`, item.snippet);
1599
+ validateSearchScore(`${label} ${index} score`, item.score);
1600
+ }
1601
+ return evidence.length;
1602
+ }
1603
+ function validateSearchEvidenceKind(label, value, id) {
1604
+ if (typeof id !== "string") {
1605
+ throw new PapiersCliResponseValidationError(`${label} id was invalid.`);
1606
+ }
1607
+ if (!SEARCH_EVIDENCE_KINDS.has(String(value))) {
1608
+ throw new PapiersCliResponseValidationError(
1609
+ `${label} was not a search evidence kind.`
1610
+ );
1611
+ }
1612
+ if (value === "passage" && isPublicIdForKind(id, "passage") || value === "highlight" && isPublicIdForKind(id, "highlight") || value === "equation" && isPublicIdForKind(id, "equation")) {
1613
+ return value;
1614
+ }
1615
+ throw new PapiersCliResponseValidationError(
1616
+ `${label} did not match the public id kind.`
1617
+ );
1618
+ }
1619
+ function validateSearchMatchedBy(label, value, options) {
1620
+ const values = assertArray(label, value);
1621
+ if (values.length === 0) {
1622
+ throw new PapiersCliResponseValidationError(`${label} was empty.`);
1623
+ }
1624
+ const seen = /* @__PURE__ */ new Set();
1625
+ for (const [index, rawReason] of values.entries()) {
1626
+ const reason = assertNonEmptyString(`${label} ${index}`, rawReason);
1627
+ if (!SEARCH_MATCH_REASONS.has(reason) || !options.allow.has(reason)) {
1628
+ throw new PapiersCliResponseValidationError(
1629
+ `${label} ${index} was not an allowed search match reason.`
1630
+ );
1631
+ }
1632
+ if (seen.has(reason)) {
1633
+ throw new PapiersCliResponseValidationError(
1634
+ `${label} contained duplicate match reasons.`
1635
+ );
1636
+ }
1637
+ seen.add(reason);
1638
+ }
1639
+ }
1640
+ function validateSearchScore(label, value) {
1641
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
1642
+ throw new PapiersCliResponseValidationError(
1643
+ `${label} was not a finite score in [0, 1].`
1644
+ );
1645
+ }
1646
+ }
1335
1647
  function assertNoInternalResponseKeys(label, value, options = {}) {
1336
1648
  const stack = [
1337
1649
  { value, isRoot: true }
@@ -1472,9 +1784,9 @@ function validateOptionalDocumentIdForKind(label, value, kind) {
1472
1784
  }
1473
1785
  return void 0;
1474
1786
  }
1475
- if (kind === "conversation") {
1787
+ if (kind === "conversation" || kind === "note") {
1476
1788
  throw new PapiersCliResponseValidationError(
1477
- `${label} was not supported for conversation items.`
1789
+ `${label} was not supported for ${kind} items.`
1478
1790
  );
1479
1791
  }
1480
1792
  return assertDocId(label, value);
@@ -1498,12 +1810,15 @@ function validateOptionalListSearchMetadata(label, value, kind) {
1498
1810
  return validatePassageListSearchMetadata(label, value);
1499
1811
  }
1500
1812
  if (kind === "highlight") {
1501
- return validateHighlightReadMetadata(label, value);
1813
+ return validateHighlightMetadata(label, value, assertDocId);
1502
1814
  }
1503
1815
  if (kind === "conversation") {
1504
1816
  validateConversationReadMetadata(label, value);
1505
1817
  return void 0;
1506
1818
  }
1819
+ if (kind === "note") {
1820
+ return validateNoteMetadata(label, value, assertDocId);
1821
+ }
1507
1822
  return validateEquationReadMetadata(label, value);
1508
1823
  }
1509
1824
  function validatePassageListSearchMetadata(label, value) {
@@ -1534,11 +1849,54 @@ function validateReadSuccessItem(label, item, kind, options) {
1534
1849
  return validateConversationReadSuccessItem(label, item, options);
1535
1850
  case "equation":
1536
1851
  return validateEquationReadSuccessItem(label, item, options);
1852
+ case "note":
1853
+ return validateNoteReadSuccessItem(label, item, options);
1537
1854
  }
1538
1855
  }
1856
+ function validateNoteReadSuccessItem(label, item, options) {
1857
+ rejectUnexpectedKeys(label, item, READ_SUCCESS_NOTE_ITEM_KEYS);
1858
+ const itemFormat = validateCliReadFormat(`${label} format`, item.format);
1859
+ if (!EXPECTED_NOTE_READ_FORMATS.some((format) => format === itemFormat)) {
1860
+ throw new PapiersCliResponseValidationError(
1861
+ `${label} format was not supported for note reads.`
1862
+ );
1863
+ }
1864
+ if (options.expectedFormat !== void 0 && !isResolvedReadFormatAllowed(itemFormat, options.expectedFormat)) {
1865
+ throw new PapiersCliResponseValidationError(
1866
+ `${label} did not echo the requested read format.`
1867
+ );
1868
+ }
1869
+ const contentResult = validateReadContent(label, item, itemFormat, {
1870
+ expectedMaxCharsPerItem: options.expectedMaxCharsPerItem
1871
+ });
1872
+ validateReadItemCursor(
1873
+ label,
1874
+ item.nextCursor,
1875
+ itemFormat,
1876
+ contentResult.truncated,
1877
+ options.singleItemRead
1878
+ );
1879
+ validateNoteMetadata(
1880
+ `${label} metadata`,
1881
+ item.metadata,
1882
+ assertDocumentReadIdentifier
1883
+ );
1884
+ const hasReadTruncatedWarning = validateOptionalReadWarnings(
1885
+ `${label} warnings`,
1886
+ item.warnings,
1887
+ itemFormat,
1888
+ "note"
1889
+ );
1890
+ if (hasReadTruncatedWarning && !contentResult.truncated) {
1891
+ throw new PapiersCliResponseValidationError(
1892
+ `${label} returned read_truncated without truncated=true.`
1893
+ );
1894
+ }
1895
+ return { ...contentResult, contentRead: true };
1896
+ }
1539
1897
  function validateDocumentReadSuccessItem(label, item, options) {
1540
1898
  rejectUnexpectedKeys(label, item, READ_SUCCESS_DOCUMENT_ITEM_KEYS);
1541
- assertDocId(`${label} id`, item.id);
1899
+ assertDocumentReadIdentifier(`${label} id`, item.id);
1542
1900
  const itemFormat = validateCliReadFormat(`${label} format`, item.format);
1543
1901
  if (options.expectedFormat !== void 0 && !isResolvedReadFormatAllowed(itemFormat, options.expectedFormat)) {
1544
1902
  throw new PapiersCliResponseValidationError(
@@ -1660,11 +2018,19 @@ function validateHighlightReadSuccessItem(label, item, options) {
1660
2018
  }
1661
2019
  if (itemFormat === "plain") {
1662
2020
  const contentResult = validatePlainReadContentFields(label, item);
1663
- validateHighlightReadMetadata(`${label} metadata`, item.metadata);
2021
+ validateHighlightMetadata(
2022
+ `${label} metadata`,
2023
+ item.metadata,
2024
+ assertDocumentReadIdentifier
2025
+ );
1664
2026
  return { ...contentResult, contentRead: true };
1665
2027
  }
1666
2028
  rejectMetadataOnlyContentFields(label, item);
1667
- validateHighlightReadMetadata(`${label} metadata`, item.metadata);
2029
+ validateHighlightMetadata(
2030
+ `${label} metadata`,
2031
+ item.metadata,
2032
+ assertDocumentReadIdentifier
2033
+ );
1668
2034
  return { returnedChars: 0, truncated: false, contentRead: false };
1669
2035
  }
1670
2036
  function validateConversationReadSuccessItem(label, item, options) {
@@ -1706,18 +2072,21 @@ function validateEquationReadSuccessItem(label, item, options) {
1706
2072
  return { returnedChars: 0, truncated: false, contentRead: false };
1707
2073
  }
1708
2074
  function validateReadItemKind(label, value, id) {
1709
- const expectedKind = publicReadKindForId(id);
1710
- if (expectedKind === null || value !== expectedKind || value !== "document" && value !== "passage" && value !== "highlight" && value !== "conversation" && value !== "equation") {
2075
+ const expectedKind = readKindForIdentifier(id);
2076
+ if (expectedKind === null || value !== expectedKind || value !== "document" && value !== "passage" && value !== "highlight" && value !== "conversation" && value !== "equation" && value !== "note") {
1711
2077
  throw new PapiersCliResponseValidationError(
1712
- `${label} did not match the public id kind.`
2078
+ `${label} did not match the read identifier kind.`
1713
2079
  );
1714
2080
  }
1715
2081
  return value;
1716
2082
  }
1717
- function validateHighlightReadMetadata(label, value) {
2083
+ function validateHighlightMetadata(label, value, validateDocumentId) {
1718
2084
  const metadata = assertRecord(label, value);
1719
2085
  rejectUnexpectedKeys(label, metadata, HIGHLIGHT_READ_METADATA_KEYS);
1720
- const documentId = assertDocId(`${label}.documentId`, metadata.documentId);
2086
+ const documentId = validateDocumentId(
2087
+ `${label}.documentId`,
2088
+ metadata.documentId
2089
+ );
1721
2090
  validateOptionalNullableString(
1722
2091
  `${label}.documentTitle`,
1723
2092
  metadata.documentTitle
@@ -1736,6 +2105,8 @@ function validateHighlightReadMetadata(label, value) {
1736
2105
  `${label}.quoteContext`,
1737
2106
  metadata.quoteContext
1738
2107
  );
2108
+ validateOptionalNullableString(`${label}.quotePrefix`, metadata.quotePrefix);
2109
+ validateOptionalNullableString(`${label}.quoteSuffix`, metadata.quoteSuffix);
1739
2110
  validateOptionalTimestamp(`${label}.createdAt`, metadata.createdAt);
1740
2111
  validateOptionalTimestamp(`${label}.updatedAt`, metadata.updatedAt);
1741
2112
  return documentId;
@@ -1767,6 +2138,21 @@ function validateEquationReadMetadata(label, value) {
1767
2138
  validateOptionalTimestamp(`${label}.updatedAt`, metadata.updatedAt);
1768
2139
  return documentId;
1769
2140
  }
2141
+ function validateNoteMetadata(label, value, validateDocumentId) {
2142
+ const metadata = assertRecord(label, value);
2143
+ rejectUnexpectedKeys(label, metadata, NOTE_READ_METADATA_KEYS);
2144
+ const documentId = metadata.documentId === null ? void 0 : validateDocumentId(`${label}.documentId`, metadata.documentId);
2145
+ if (metadata.attachedTitle !== null && typeof metadata.attachedTitle !== "string") {
2146
+ throw new PapiersCliResponseValidationError(
2147
+ `${label}.attachedTitle was not a string or null.`
2148
+ );
2149
+ }
2150
+ assertTimestampString(`${label}.editedAt`, metadata.editedAt);
2151
+ assertTimestampString(`${label}.createdAt`, metadata.createdAt);
2152
+ assertTimestampString(`${label}.updatedAt`, metadata.updatedAt);
2153
+ assertNonEmptyString(`${label}.title`, metadata.title);
2154
+ return documentId;
2155
+ }
1770
2156
  function rejectUnexpectedContent(label, item) {
1771
2157
  if (item.content !== void 0 && item.content !== null) {
1772
2158
  throw new PapiersCliResponseValidationError(
@@ -1994,19 +2380,20 @@ function validateReadUsage(label, rawUsage, options) {
1994
2380
  function validateReadItemError(label, value, kind, expectedFormat) {
1995
2381
  const error = assertRecord(label, value);
1996
2382
  rejectUnexpectedKeys(label, error, READ_ITEM_ERROR_KEYS);
1997
- if (error.code !== DOCUMENT_READ_NOT_FOUND_CODE) {
1998
- if (kind !== "document" && kind !== "passage" || error.code !== DOCUMENT_READ_SOURCE_SUPERSEDED_CODE) {
1999
- throw new PapiersCliResponseValidationError(
2000
- `${label}.code was not a supported read error code.`
2001
- );
2002
- }
2383
+ const sourceSupersededAllowed = error.code === DOCUMENT_READ_SOURCE_SUPERSEDED_CODE && (kind === "document" || kind === "passage" || kind === "note" || kind === "conversation");
2384
+ const contentLimitAllowed = error.code === CONVERSATION_READ_CONTENT_LIMIT_CODE && kind === "conversation";
2385
+ const requestLimitAllowed = error.code === CONVERSATION_READ_REQUEST_LIMIT_CODE && kind === "conversation";
2386
+ if (error.code !== DOCUMENT_READ_NOT_FOUND_CODE && !sourceSupersededAllowed && !contentLimitAllowed && !requestLimitAllowed) {
2387
+ throw new PapiersCliResponseValidationError(
2388
+ `${label}.code was not a supported read error code.`
2389
+ );
2003
2390
  }
2004
- if (expectedFormat === "metadata" && error.code === DOCUMENT_READ_SOURCE_SUPERSEDED_CODE) {
2391
+ if (expectedFormat === "metadata" && (error.code === DOCUMENT_READ_SOURCE_SUPERSEDED_CODE || error.code === CONVERSATION_READ_CONTENT_LIMIT_CODE || error.code === CONVERSATION_READ_REQUEST_LIMIT_CODE)) {
2005
2392
  throw new PapiersCliResponseValidationError(
2006
2393
  `${label}.code was not supported for metadata reads.`
2007
2394
  );
2008
2395
  }
2009
- const expectedMessage = error.code === DOCUMENT_READ_NOT_FOUND_CODE ? readNotFoundMessageForKind(kind) : kind === "passage" ? PASSAGE_READ_CONTENT_UNAVAILABLE_MESSAGE : DOCUMENT_READ_SOURCE_SUPERSEDED_MESSAGE;
2396
+ const expectedMessage = error.code === DOCUMENT_READ_NOT_FOUND_CODE ? readNotFoundMessageForKind(kind) : error.code === CONVERSATION_READ_CONTENT_LIMIT_CODE ? CONVERSATION_READ_CONTENT_LIMIT_MESSAGE : error.code === CONVERSATION_READ_REQUEST_LIMIT_CODE ? CONVERSATION_READ_REQUEST_LIMIT_MESSAGE : kind === "conversation" ? CONVERSATION_READ_SOURCE_SUPERSEDED_MESSAGE : kind === "passage" ? PASSAGE_READ_CONTENT_UNAVAILABLE_MESSAGE : kind === "note" ? NOTE_READ_SOURCE_SUPERSEDED_MESSAGE : DOCUMENT_READ_SOURCE_SUPERSEDED_MESSAGE;
2010
2397
  if (error.message !== expectedMessage) {
2011
2398
  throw new PapiersCliResponseValidationError(
2012
2399
  `${label}.message was not a supported read error message.`
@@ -2031,6 +2418,8 @@ function readNotFoundMessageForKind(kind) {
2031
2418
  return CONVERSATION_READ_NOT_FOUND_MESSAGE;
2032
2419
  case "equation":
2033
2420
  return EQUATION_READ_NOT_FOUND_MESSAGE;
2421
+ case "note":
2422
+ return NOTE_READ_NOT_FOUND_MESSAGE;
2034
2423
  }
2035
2424
  }
2036
2425
  function validateOptionalSearchQuality(label, value) {
@@ -2095,7 +2484,7 @@ function validateOptionalReadWarnings(label, value, format, kind) {
2095
2484
  );
2096
2485
  }
2097
2486
  hasReadTruncatedWarning = true;
2098
- const expectedMessage = kind === "document" ? DOCUMENT_READ_TRUNCATED_WARNING_MESSAGE : PASSAGE_READ_TRUNCATED_WARNING_MESSAGE;
2487
+ const expectedMessage = kind === "document" ? DOCUMENT_READ_TRUNCATED_WARNING_MESSAGE : kind === "passage" ? PASSAGE_READ_TRUNCATED_WARNING_MESSAGE : NOTE_READ_TRUNCATED_WARNING_MESSAGE;
2099
2488
  if (warning.message !== expectedMessage) {
2100
2489
  throw new PapiersCliResponseValidationError(
2101
2490
  `${label} ${index}.message was not a supported warning message.`
@@ -2515,10 +2904,18 @@ function assertDocId(label, value) {
2515
2904
  }
2516
2905
  return value;
2517
2906
  }
2518
- function assertReadPublicId(label, value) {
2519
- if (typeof value !== "string" || publicReadKindForId(value) === null) {
2907
+ function assertDocumentReadIdentifier(label, value) {
2908
+ if (typeof value !== "string" || !isDocumentReadIdentifier(value)) {
2520
2909
  throw new PapiersCliResponseValidationError(
2521
- `${label} was not a supported public read id.`
2910
+ `${label} was not a doc_* public id or papiers://item locator.`
2911
+ );
2912
+ }
2913
+ return value;
2914
+ }
2915
+ function assertReadIdentifier(label, value) {
2916
+ if (typeof value !== "string" || readKindForIdentifier(value) === null) {
2917
+ throw new PapiersCliResponseValidationError(
2918
+ `${label} was not a supported read identifier.`
2522
2919
  );
2523
2920
  }
2524
2921
  return value;
@@ -2662,7 +3059,7 @@ async function readDocuments(config, fetchImpl, request) {
2662
3059
  "/api/v1/papiers/read",
2663
3060
  { method: "POST", body: request, retryable: true }
2664
3061
  );
2665
- const readKind = publicReadKindForId(request.ids[0] ?? "");
3062
+ const readKind = readKindForIdentifier(request.ids[0] ?? "");
2666
3063
  assertPapiersReadResponse("read", body, request.ids, {
2667
3064
  // Every kind resolves an omitted format server-side (auto semantics),
2668
3065
  // so there is nothing to echo-check unless the caller pinned one;
@@ -2709,22 +3106,18 @@ function expectedKindForListSearchRequest(request) {
2709
3106
  function expectedKindsForSearchRequest(request) {
2710
3107
  const kinds = request.kinds;
2711
3108
  if (kinds === void 0) {
2712
- return {
2713
- expectedKinds: /* @__PURE__ */ new Set([
2714
- "document",
2715
- "passage",
2716
- "highlight",
2717
- "conversation"
2718
- ])
2719
- };
3109
+ return { expectedKinds: /* @__PURE__ */ new Set(["document", "conversation", "note"]) };
2720
3110
  }
2721
- if (!Array.isArray(kinds) || kinds.length <= 1) {
2722
- return { expectedKind: expectedKindForListSearchRequest(request) };
3111
+ if (!Array.isArray(kinds) || kinds.length === 0) {
3112
+ throw new CliUsageError(
3113
+ "Public search requests must specify at least one supported kind."
3114
+ );
2723
3115
  }
2724
- for (const kind of kinds) {
2725
- if (kind === "document" || kind === "equation") {
3116
+ const rawKinds = kinds;
3117
+ for (const kind of rawKinds) {
3118
+ if (kind === "document") {
2726
3119
  throw new CliUsageError(
2727
- `Multi-kind public search does not support kind ${kind}; request it separately.`
3120
+ "Unified public search does not support kind document; omit kinds to search titles plus content."
2728
3121
  );
2729
3122
  }
2730
3123
  if (!PUBLIC_KINDS.includes(kind)) {
@@ -2733,14 +3126,19 @@ function expectedKindsForSearchRequest(request) {
2733
3126
  );
2734
3127
  }
2735
3128
  }
2736
- return {
2737
- expectedKinds: new Set(kinds)
2738
- };
3129
+ const expectedKinds = /* @__PURE__ */ new Set();
3130
+ for (const kind of rawKinds) {
3131
+ expectedKinds.add(
3132
+ kind === "conversation" || kind === "note" ? kind : "document"
3133
+ );
3134
+ }
3135
+ const [onlyKind] = expectedKinds;
3136
+ return expectedKinds.size === 1 ? { expectedKind: onlyKind } : { expectedKinds };
2739
3137
  }
2740
3138
  async function requestPapiersApi(config, fetchImpl, path, init) {
2741
3139
  if (!config.token) {
2742
3140
  throw new CliUsageError(
2743
- `${TOKEN_ENV_VAR} is required for public Papiers API commands.`
3141
+ `${TOKEN_ENV_VAR} is not set. Run papiers auth login, or set ${TOKEN_ENV_VAR}, then retry.`
2744
3142
  );
2745
3143
  }
2746
3144
  return requestPapiersApiWithBearer(
@@ -2862,7 +3260,7 @@ var PMID_PATTERN = /^\d+$/u;
2862
3260
  var YEAR_PATTERN = /^\d{4}$/u;
2863
3261
  var MIN_PUBLIC_YEAR = 1;
2864
3262
  var MAX_PUBLIC_YEAR = 9999;
2865
- var DOCUMENT_FILTER_FIELD_ALIASES = {
3263
+ var FORMER_FILTER_FIELD_ALIASES = {
2866
3264
  arxiv_id: "arxivId",
2867
3265
  last_engaged_at: "lastEngagedAt",
2868
3266
  published_date: "publishedDate",
@@ -2871,13 +3269,27 @@ var DOCUMENT_FILTER_FIELD_ALIASES = {
2871
3269
  source_family: "sourceFamily",
2872
3270
  year: "publishedYear"
2873
3271
  };
2874
- var DOCUMENT_FILTER_FIELD_ALIAS_NAMES = Object.keys(
2875
- DOCUMENT_FILTER_FIELD_ALIASES
2876
- ).sort();
3272
+ function didYouMeanFilterField(field) {
3273
+ const suggestion = FORMER_FILTER_FIELD_ALIASES[field];
3274
+ return suggestion ? ` Did you mean "${suggestion}"?` : "";
3275
+ }
2877
3276
  function parseDocumentFilterExpression(expression) {
3277
+ if (isImplicitAuthorEqualityFilterExpression(expression)) {
3278
+ throw new CliUsageError(
3279
+ 'Use "author.contains=<name-or-handle>" for author searches, or "author.eq=<full-author-key>" for exact author-key matches.'
3280
+ );
3281
+ }
2878
3282
  const filter = parseFilterExpression(expression);
2879
3283
  return normalizeSupportedDocumentFilter(filter);
2880
3284
  }
3285
+ function isImplicitAuthorEqualityFilterExpression(expression) {
3286
+ const trimmed = expression.trim();
3287
+ const equalsIndex = trimmed.indexOf("=");
3288
+ if (equalsIndex < 0) {
3289
+ return false;
3290
+ }
3291
+ return trimmed.slice(0, equalsIndex).trim() === "author";
3292
+ }
2881
3293
  function parseFilterExpression(expression) {
2882
3294
  const parsed = parseFilterParts(expression);
2883
3295
  const field = parsed.field.trim();
@@ -2903,57 +3315,52 @@ function parseFilterExpression(expression) {
2903
3315
  };
2904
3316
  }
2905
3317
  function normalizeSupportedDocumentFilter(filter) {
2906
- const canonicalField = canonicalDocumentFilterField(filter.field);
2907
- const canonicalFilter = canonicalField === filter.field ? filter : { ...filter, field: canonicalField };
2908
- const allowedOperators = EXPECTED_DOCUMENT_FILTER_OPERATORS[canonicalField];
3318
+ const field = filter.field;
3319
+ const allowedOperators = EXPECTED_DOCUMENT_FILTER_OPERATORS[field];
2909
3320
  if (!allowedOperators) {
2910
3321
  throw new CliUsageError(
2911
3322
  `Unsupported public document filter field "${filter.field}". Supported fields: ${Object.keys(
2912
3323
  EXPECTED_DOCUMENT_FILTER_OPERATORS
2913
- ).join(", ")}.`
3324
+ ).join(", ")}.${didYouMeanFilterField(filter.field)}`
2914
3325
  );
2915
3326
  }
2916
- if (!allowedOperators.includes(
2917
- canonicalFilter.op
2918
- )) {
3327
+ if (!allowedOperators.includes(filter.op)) {
2919
3328
  throw new CliUsageError(
2920
- `Operator "${canonicalFilter.op}" is not supported for public document filter field "${canonicalFilter.field}". Supported operators: ${allowedOperators.join(
3329
+ `Operator "${filter.op}" is not supported for public document filter field "${filter.field}". Supported operators: ${allowedOperators.join(
2921
3330
  ", "
2922
3331
  )}.`
2923
3332
  );
2924
3333
  }
2925
- switch (canonicalFilter.field) {
3334
+ const documentFilter = { ...filter, field };
3335
+ switch (field) {
2926
3336
  case "id":
2927
3337
  return normalizeStringDocumentFilter(
2928
- canonicalFilter,
3338
+ documentFilter,
2929
3339
  normalizeDocumentIdValue
2930
3340
  );
2931
3341
  case "doi":
2932
- return normalizeStringDocumentFilter(canonicalFilter, normalizeDoiOrNull);
3342
+ return normalizeStringDocumentFilter(documentFilter, normalizeDoiOrNull);
2933
3343
  case "arxivId":
2934
3344
  return normalizeStringDocumentFilter(
2935
- canonicalFilter,
3345
+ documentFilter,
2936
3346
  normalizeArxivOrNull
2937
3347
  );
2938
3348
  case "pmid":
2939
- return normalizeStringDocumentFilter(
2940
- canonicalFilter,
2941
- normalizePmidOrNull
2942
- );
3349
+ return normalizeStringDocumentFilter(documentFilter, normalizePmidOrNull);
2943
3350
  case "publishedDate":
2944
- return normalizePublishedDateFilter(canonicalFilter);
3351
+ return normalizePublishedDateFilter(documentFilter);
2945
3352
  case "publishedYear":
2946
- return normalizePublishedYearFilter(canonicalFilter);
3353
+ return normalizePublishedYearFilter(documentFilter);
2947
3354
  case "sourceDomain":
2948
- return normalizeSourceDomainFilter(canonicalFilter);
3355
+ return normalizeSourceDomainFilter(documentFilter);
2949
3356
  case "annotated":
2950
3357
  case "engaged":
2951
- return normalizeBooleanDocumentFilter(canonicalFilter);
3358
+ return normalizeBooleanDocumentFilter(documentFilter);
2952
3359
  case "lastEngagedAt":
2953
- return normalizeLastEngagedAtFilter(canonicalFilter);
3360
+ return normalizeLastEngagedAtFilter(documentFilter);
2954
3361
  default:
2955
3362
  return normalizeStringDocumentFilter(
2956
- canonicalFilter,
3363
+ documentFilter,
2957
3364
  normalizeNonEmptyString
2958
3365
  );
2959
3366
  }
@@ -3001,12 +3408,6 @@ function normalizeBooleanDocumentFilter(filter) {
3001
3408
  }
3002
3409
  throw invalidFilterValue(filter);
3003
3410
  }
3004
- function canonicalDocumentFilterField(field) {
3005
- return Object.prototype.hasOwnProperty.call(
3006
- DOCUMENT_FILTER_FIELD_ALIASES,
3007
- field
3008
- ) ? DOCUMENT_FILTER_FIELD_ALIASES[field] : field;
3009
- }
3010
3411
  function normalizePublishedDateFilter(filter) {
3011
3412
  const normalized = normalizePapiersPublicDateFilter(
3012
3413
  filter.op,
@@ -3293,21 +3694,22 @@ function parseScalar(rawValue) {
3293
3694
 
3294
3695
  // src/args.ts
3295
3696
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3697
+ "all",
3296
3698
  "force",
3297
3699
  "help",
3298
3700
  "doc-ids",
3701
+ "evidence-ids",
3702
+ "highlight-ids",
3299
3703
  "ids",
3300
3704
  "ids0",
3301
3705
  "input0",
3302
3706
  "json",
3707
+ "passage-ids",
3303
3708
  "pretty",
3304
- "stdin",
3305
- "token-stdin",
3306
3709
  "fail-fast",
3307
3710
  "version"
3308
3711
  ]);
3309
3712
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
3310
- "api-base-url",
3311
3713
  "api-url",
3312
3714
  "cursor",
3313
3715
  "document-id",
@@ -3326,8 +3728,13 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
3326
3728
  "since"
3327
3729
  ]);
3328
3730
  var SECRET_ARG_FLAGS = /* @__PURE__ */ new Set(["pat", "token", "api-token"]);
3329
- var GLOBAL_VALUE_FLAGS = /* @__PURE__ */ new Set(["api-base-url", "api-url"]);
3731
+ var GLOBAL_VALUE_FLAGS = /* @__PURE__ */ new Set(["api-url"]);
3330
3732
  var NO_COMMAND_VALUE_FLAGS = /* @__PURE__ */ new Set([...GLOBAL_VALUE_FLAGS]);
3733
+ var REMOVED_FLAG_HINTS = {
3734
+ "api-base-url": "Use --api-url instead.",
3735
+ "token-stdin": "papiers auth login reads the token from stdin \u2014 no flag needed.",
3736
+ stdin: "papiers auth login reads the token from stdin \u2014 no flag needed."
3737
+ };
3331
3738
  var NO_COMMAND_BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["help", "json"]);
3332
3739
  var IDS_PIPELINE_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3333
3740
  "help",
@@ -3338,6 +3745,12 @@ var IDS_PIPELINE_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3338
3745
  "json",
3339
3746
  "pretty"
3340
3747
  ]);
3748
+ var SEARCH_IDS_PIPELINE_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3749
+ ...IDS_PIPELINE_BOOLEAN_FLAGS,
3750
+ "evidence-ids",
3751
+ "highlight-ids",
3752
+ "passage-ids"
3753
+ ]);
3341
3754
  var LIST_VALUE_FLAGS = /* @__PURE__ */ new Set([
3342
3755
  ...GLOBAL_VALUE_FLAGS,
3343
3756
  "cursor",
@@ -3378,122 +3791,320 @@ var READ_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3378
3791
  ]);
3379
3792
  var AUTH_LOGIN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3380
3793
  ...NO_COMMAND_BOOLEAN_FLAGS,
3381
- "force",
3382
- "stdin",
3383
- "token-stdin"
3794
+ "force"
3384
3795
  ]);
3385
- var PUBLIC_KIND_ALIAS_HELP = "Kind aliases: doc/docs/documents -> document, annotation(s) -> highlight, chat(s) -> conversation; plural kind names are accepted.";
3386
- var PUBLIC_KIND_ALIASES = /* @__PURE__ */ new Map([
3387
- ["document", "document"],
3388
- ["documents", "document"],
3796
+ var FORMER_KIND_ALIASES = /* @__PURE__ */ new Map([
3389
3797
  ["doc", "document"],
3390
3798
  ["docs", "document"],
3391
- ["passage", "passage"],
3799
+ ["documents", "document"],
3392
3800
  ["passages", "passage"],
3393
- ["highlight", "highlight"],
3394
- ["highlights", "highlight"],
3395
3801
  ["annotation", "highlight"],
3396
3802
  ["annotations", "highlight"],
3397
- ["conversation", "conversation"],
3398
- ["conversations", "conversation"],
3803
+ ["highlights", "highlight"],
3399
3804
  ["chat", "conversation"],
3400
3805
  ["chats", "conversation"],
3401
- ["equation", "equation"],
3402
- ["equations", "equation"]
3806
+ ["conversations", "conversation"],
3807
+ ["equations", "equation"],
3808
+ ["notes", "note"]
3403
3809
  ]);
3404
3810
  var MULTI_SEARCH_KINDS = [
3405
3811
  "passage",
3406
3812
  "highlight",
3407
- "conversation"
3813
+ "conversation",
3814
+ "equation",
3815
+ "note"
3408
3816
  ];
3409
- var HELP_TEXT = `Papiers CLI
3817
+ var GLOBAL_HELP = `Papiers CLI: search and read your library from the terminal.
3818
+ Your saved papers, highlights, notes, and conversations (your chats) \u2014 for you or an AI agent.
3410
3819
 
3411
- Search, list, and read your saved papers, highlights, notes, and conversations from the terminal.
3412
- Data commands return stable JSON by default. Use --pretty for human-readable output.
3820
+ GETTING STARTED
3821
+ papiers auth login Sign in with a token from Papiers (Settings -> API tokens)
3822
+ papiers list --limit 5 --pretty Your most recent documents
3823
+ papiers search "your topic" --pretty Search across your library
3413
3824
 
3414
- Usage:
3415
- papiers auth login --token-stdin [--force] [--json]
3416
- papiers auth status [--json]
3417
- papiers auth inspect [--json]
3418
- papiers auth logout [--json]
3419
- papiers doctor [--json]
3420
- papiers capabilities [--json]
3825
+ COMMANDS
3826
+ search Search across your library
3827
+ list List your documents, highlights, notes, and conversations
3828
+ read Print a document, highlight, note, or conversation
3829
+ browse Page through an earlier search's results
3830
+ auth Sign in, check status, or sign out
3831
+ tokens List or revoke your API tokens
3832
+ doctor Check your setup, connection, and what's available
3833
+ version Print the CLI version
3834
+
3835
+ papiers <command> --help full options and examples for a command
3836
+ papiers help --all everything, in one page
3837
+
3838
+ OUTPUT
3839
+ Commands print JSON by default \u2014 built for scripts and agents.
3840
+ --pretty human-readable output
3841
+ --ids / --doc-ids pipe result IDs into the next command
3842
+
3843
+ SIGNING IN
3844
+ Signing in saves your token on this machine.
3845
+ For scripts or an AI agent, set PAPIERS_TOKEN instead.
3846
+
3847
+ Exit codes: 0 ok \xB7 1 command or API failure \xB7 2 bad usage`;
3848
+ var OUTPUT_OPTS = ` --json machine-readable JSON (the default)
3849
+ --pretty human-readable text
3850
+ --ids result/item IDs, one per line, with their native prefix
3851
+ --ids0 the same IDs, NUL-separated, for \`xargs -0\`
3852
+ --doc-ids parent document IDs only (doc_\u2026), deduped; conversation hits are skipped`;
3853
+ var SEARCH_OUTPUT_OPTS = `${OUTPUT_OPTS}
3854
+ --passage-ids matching passage evidence IDs, flattened in result order
3855
+ --highlight-ids matching highlight evidence IDs, flattened in result order
3856
+ --evidence-ids all matching evidence IDs, flattened in result order`;
3857
+ var HELP_TOPICS = {
3858
+ search: `papiers search \u2014 search across your library.
3859
+
3860
+ USAGE
3861
+ papiers search QUERY [--kind KIND ...] [--filter FIELD.OP=VALUE ...] [--limit N] [--pretty|--json|--ids|--ids0|--doc-ids|--passage-ids|--highlight-ids|--evidence-ids]
3862
+
3863
+ OPTIONS
3864
+ --kind KIND what to search. Defaults to all \u2014 your passages, highlights,
3865
+ equations, notes, conversations, and matching titles. Or focus on one:
3866
+ passage \xB7 highlight \xB7 conversation \xB7 equation \xB7 note.
3867
+ --filter FIELD.OP=VALUE narrow parent documents \u2014 repeatable, up to 25.
3868
+ e.g. --filter publishedYear.gte=2020 --filter author.contains=hinton. Fields/ops: help --all.
3869
+ --document-id ID scope to documents: doc_\u2026 (comma-separated or repeated), or @-/@file.
3870
+ --limit N max results, 1\u201350.
3871
+ ${SEARCH_OUTPUT_OPTS}
3872
+
3873
+ EXAMPLES
3874
+ papiers search "mixture of experts" --pretty
3875
+ papiers search "capacity factors" --kind passage --document-id doc_<id> --json
3876
+ papiers search "dropout" --filter author.contains=hinton --json
3877
+ papiers search "diffusion" --filter venue.contains=neurips --json
3878
+ papiers search retrieval --kind passage --limit 5 --passage-ids | papiers read @- --format plain --json
3879
+
3880
+ When capabilities.searchResultsBrowse.enabled is true, a search may return ranking.searchId \u2014 pass it to \`papiers browse\` for more pages.`,
3881
+ list: `papiers list \u2014 list your documents, highlights, notes, and conversations.
3882
+
3883
+ USAGE
3884
+ papiers list [--kind KIND] [--limit N] [--cursor CURSOR] [--pretty|--json|--ids|--ids0|--doc-ids]
3885
+ papiers list --kind document [--filter FIELD.OP=VALUE ...] [--order-by KEY] [--document-id doc_\u2026|@-|@file]
3886
+ papiers list --kind highlight|conversation [--since WHEN]
3887
+
3888
+ OPTIONS
3889
+ --kind KIND document (default) \xB7 passage \xB7 highlight \xB7 conversation \xB7 equation \xB7 note.
3890
+ --filter FIELD.OP=VALUE narrow document results \u2014 repeatable, up to 25. document kind only.
3891
+ e.g. --filter lastEngagedAt.gte=30d --filter annotated=true. Fields/ops: help --all.
3892
+ --order-by KEY addedAt:desc (default) or lastEngagedAt:desc. document kind only.
3893
+ --document-id ID scope to documents: doc_\u2026 (comma-separated or repeated), or @-/@file.
3894
+ document and highlight only.
3895
+ --since WHEN only items at or after an ISO 8601 time, or an Nd shorthand (e.g. 30d).
3896
+ highlight and conversation only.
3897
+ --limit N max results, 1\u201350.
3898
+ --cursor CURSOR fetch the next page \u2014 use page.nextCursor from the previous response.
3899
+ ${OUTPUT_OPTS}
3900
+
3901
+ EXAMPLES
3902
+ papiers list --limit 5 --pretty
3903
+ papiers list --kind highlight --since 30d --pretty
3904
+ papiers list --kind document --filter lastEngagedAt.gte=30d --json
3905
+ papiers list --kind document --filter annotated=true --json`,
3906
+ read: `papiers read \u2014 print documents, highlights, notes, conversations, passages, or equations.
3907
+
3908
+ USAGE
3909
+ papiers read ID [ID ...] [--format FORMAT] [--max-chars-per-item N] [--fail-fast] [--json|--pretty]
3910
+ papiers read @- | @file [--input0] [--format FORMAT] [--max-chars-per-item N] [--json|--pretty]
3911
+ Read public IDs or papiers://note|highlight|item|conversation/<uuid> locators; don't mix resource kinds.
3912
+
3913
+ OPTIONS
3914
+ --format FORMAT how to render each item. Default auto (equation defaults to metadata).
3915
+ document: auto|metadata|markdown|plain. passage: markdown|plain.
3916
+ highlight, conversation: metadata|plain. note: markdown|plain.
3917
+ equation: metadata.
3918
+ --include SECTION extra sections, comma-separated: metadata, source, locations.
3919
+ document only (highlight/conversation/equation/note: metadata only; passage: none).
3920
+ --pages RANGE restrict a document read to pages, e.g. 1,3-5. document only.
3921
+ --max-chars-per-item N truncate each item's content to N chars, 1\u2013200000.
3922
+ --cursor CURSOR continue one long document or note \u2014 use items[].nextCursor from the
3923
+ previous response. One literal document or note identifier only, with
3924
+ --format auto, markdown, or plain.
3925
+ --fail-fast exit non-zero if any item fails (default: succeed if at least one does).
3926
+ --json machine-readable JSON (the default)
3927
+ --pretty human-readable text
3928
+
3929
+ EXAMPLES
3930
+ papiers read doc_<id> --format markdown --max-chars-per-item 2000 --json
3931
+ papiers read nte_<id> --format markdown --json
3932
+ papiers read hl_<id> --json
3933
+ papiers search retrieval --doc-ids | papiers read @- --format metadata --json`,
3934
+ browse: `papiers browse \u2014 page deeper into an earlier search's results.
3935
+
3936
+ USAGE
3937
+ papiers browse srch_\u2026 [--offset N] [--limit N] [--pretty|--json|--ids|--ids0|--doc-ids|--passage-ids|--highlight-ids|--evidence-ids]
3938
+
3939
+ OPTIONS
3940
+ --offset N start at rank N (0-based) in the frozen ranking.
3941
+ --limit N max results, 1\u201350.
3942
+ ${SEARCH_OUTPUT_OPTS}
3943
+
3944
+ When capabilities.searchResultsBrowse.enabled is true, a search response includes
3945
+ ranking.searchId (an srch_\u2026 handle); pass it here to page deeper without
3946
+ re-running the search. The ranking is frozen and expires after a while.
3947
+
3948
+ EXAMPLE
3949
+ papiers browse srch_<id> --offset 10 --json`,
3950
+ auth: `papiers auth \u2014 sign in, check status, or sign out.
3951
+
3952
+ USAGE
3953
+ papiers auth login [--force] [--json] Sign in (paste your token, or pipe it in)
3954
+ papiers auth status [--json] Show whether you're signed in
3955
+ papiers auth inspect [--json] Show details about the saved token
3956
+ papiers auth logout [--json] Forget the saved token
3957
+
3958
+ OPTIONS
3959
+ --force when signing in, replace an already-saved token.
3960
+
3961
+ Signing in saves your token on this machine. For scripts or an AI agent, set PAPIERS_TOKEN instead.`,
3962
+ tokens: `papiers tokens \u2014 list or revoke your API tokens.
3963
+
3964
+ USAGE
3421
3965
  papiers tokens list [--json]
3422
3966
  papiers tokens revoke TOKEN_ID [--json]
3423
- papiers version | papiers --version | papiers -V
3424
- papiers list [--kind document] [--limit 20] [--cursor CURSOR] [--filter FIELD.OP=VALUE] [--document-id doc_...|@-|@file] [--input0] [--order-by addedAt:desc|lastEngagedAt:desc] [--json|--ids|--ids0|--doc-ids|--pretty]
3425
- papiers list --kind passage|highlight|conversation|equation [--document-id doc_... (highlight)] [--since ISO|Nd (highlight/conversation)] [--limit 20] [--cursor CURSOR] [--json|--ids|--ids0|--doc-ids|--pretty]
3426
- papiers search QUERY [--limit 20] [--json|--ids|--ids0|--doc-ids|--pretty]
3427
- papiers search QUERY --kind document [--query-in title|metadata] [--limit 20] [--filter FIELD.OP=VALUE] [--document-id doc_...|@-|@file] [--input0] [--json|--ids|--ids0|--doc-ids|--pretty]
3428
- papiers search QUERY --kind passage|highlight|conversation [--query-in content] [--document-id doc_... (passage/highlight)] [--since ISO|Nd (highlight/conversation)] [--limit 20] [--json|--ids|--ids0|--doc-ids|--pretty]
3429
- papiers search QUERY --kind all [--limit 20] [--json|--ids|--ids0|--doc-ids|--pretty]
3430
- papiers search QUERY --kind equation [--query-in latex] [--limit 20] [--json|--ids|--ids0|--doc-ids|--pretty]
3431
- papiers browse srch_... [--offset N] [--limit 20] [--json|--ids|--ids0|--doc-ids|--pretty]
3432
- papiers read doc_... [doc_...] [--id doc_...|@file] [--kind document] [--format auto|metadata|markdown|plain] [--include metadata,source,locations] [--pages 1,3-5] [--max-chars-per-item N] [--input0] [--fail-fast] [--json|--pretty]
3433
- papiers read doc_... --format auto|markdown|plain --cursor CURSOR [--pages 1,3-5] [--max-chars-per-item N] [--json]
3434
- papiers read pas_... [pas_...] [--format markdown|plain] [--max-chars-per-item N] [--input0] [--fail-fast] [--json|--pretty]
3435
- papiers read hl_... [hl_...] [--format metadata|plain|auto] [--max-chars-per-item N] [--input0] [--fail-fast] [--json|--pretty]
3436
- papiers read cnv_... [cnv_...] [--format metadata|plain|auto] [--max-chars-per-item N] [--input0] [--fail-fast] [--json|--pretty]
3437
- papiers read eq_... [eq_...] [--format metadata|auto] [--include metadata] [--input0] [--fail-fast] [--json|--pretty]
3438
- papiers read @- [--input0] [--format auto|metadata|markdown|plain] [--include metadata,source,locations] [--pages 1,3-5] [--max-chars-per-item N] [--fail-fast] [--json]
3439
3967
 
3440
- Examples:
3441
- papiers capabilities --json # check available features
3442
- papiers list --kind document --limit 5 --json
3443
- papiers list --kind annotations --limit 5 --pretty # your highlights with notes
3444
- papiers list --kind annotations --since 30d --json # recent highlights
3445
- papiers list --kind annotations --document-id doc_<id> --json # highlights on one document
3446
- papiers list --kind document --filter engaged=false --limit 10 --json # unread documents
3447
- papiers list --kind document --filter annotated=true --json # documents with your notes
3448
- papiers list --kind document --filter lastEngagedAt.gte=30d --json # recently active documents
3449
- papiers list --kind document --order-by lastEngagedAt:desc --limit 10 --json # most recently engaged first (engaged docs only)
3450
- papiers search "router load balancing tradeoffs" --limit 10 --json
3451
- papiers browse srch_<id> --offset 10 --json
3452
- papiers search "mixture of experts" --kind passage --limit 5 --json
3453
- papiers search "capacity factors" --kind passage --document-id doc_<id> --json # search within one document
3454
- papiers search "evaluation harness" --kind chats --json
3455
- papiers search "\\\\alpha" --kind equation --query-in latex --json
3456
- papiers read doc_<id> --format markdown --max-chars-per-item 2000 --json
3457
- papiers read hl_<id> --json # highlight quote + annotation
3458
- papiers search retrieval --kind passage --limit 5 --ids | papiers read @- --format plain --json
3459
- papiers search retrieval --doc-ids | papiers read @- --format metadata --json # resolve hits to their parent documents
3968
+ Create and name tokens in Papiers (Settings -> API tokens). Revoking is permanent.`,
3969
+ doctor: `papiers doctor \u2014 check your setup, connection, and what's available.
3460
3970
 
3461
- Output:
3462
- Data commands return JSON by default for scripts.
3463
- Use --pretty for human-readable terminal output.
3464
- Use --ids or --ids0 to pipe result IDs into another command.
3465
- Use --doc-ids to pipe parent document IDs where available.
3466
- Paginated responses include page.nextCursor; pass it back with --cursor.
3467
- Search responses may include ranking.searchId; pass it to papiers browse.
3468
- API errors use papiers.error.v1 with error.message and requestId.
3971
+ USAGE
3972
+ papiers doctor [--json]
3469
3973
 
3470
- Exit codes:
3471
- 0 success; 1 command or API failure (read exits 1 when no item succeeded,
3472
- or with --fail-fast when any item failed); 2 usage or configuration error.
3974
+ Verifies your token, the server connection, and which features are available.
3975
+ Run it first when something isn't working.`,
3976
+ version: `papiers version \u2014 print the CLI version.
3473
3977
 
3474
- Configuration:
3475
- Create an API token in Papiers, then run papiers auth login --token-stdin.
3476
- For CI or scripts, set PAPIERS_TOKEN and pass --json for stable output.
3978
+ USAGE
3979
+ papiers version | papiers --version | papiers -V`
3980
+ };
3981
+ var REFERENCE = `REFERENCE \u2014 IDs, piping, pagination, and limits.
3477
3982
 
3478
- Tokens must come from PAPIERS_TOKEN or an ignored local .env file, not command-line flags.
3479
- Read cursors apply only to one literal doc_* ID with --format auto, markdown, or plain.
3480
- ${PUBLIC_KIND_ALIAS_HELP}
3481
- Filter aliases accepted by the CLI include ${DOCUMENT_FILTER_FIELD_ALIAS_NAMES.join(", ")}.
3482
- `;
3983
+ ID PREFIXES
3984
+ doc_ document pas_ passage hl_ highlight
3985
+ cnv_ conversation eq_ equation nte_ note
3986
+ srch_ a saved search ranking (for browse)
3987
+ On read, the kind \u2014 and which --format/--include apply \u2014 is read from the prefix.
3988
+
3989
+ RESOURCE LOCATORS
3990
+ papiers://note/<uuid> papiers://highlight/<uuid>
3991
+ papiers://item/<uuid> papiers://conversation/<uuid>
3992
+ Locators are accepted by read and resolved with the same ownership checks as public IDs.
3993
+
3994
+ PIPING IDS BETWEEN COMMANDS
3995
+ --ids result IDs, one per line, with their native prefix
3996
+ --ids0 the same IDs, NUL-separated (for \`xargs -0\`)
3997
+ --doc-ids parent document IDs only (doc_\u2026), deduped; conversation hits are skipped
3998
+ @- read identifiers from stdin @file read identifiers from a file
3999
+ --input0 read piped identifiers NUL-separated instead of whitespace/newlines
4000
+ @- and @file accept IDs, resource locators, or a Papiers JSON envelope, and may be mixed with literals.
4001
+
4002
+ PAGINATION \u2014 three separate mechanisms
4003
+ list response has page.nextCursor \u2192 papiers list \u2026 --cursor CURSOR
4004
+ search response has ranking.searchId \u2192 papiers browse srch_\u2026 --offset N
4005
+ read a truncated item has items[].nextCursor \u2192 papiers read NOTE_OR_DOCUMENT --cursor CURSOR
4006
+
4007
+ LIMITS
4008
+ --limit 1\u201350 \xB7 --max-chars-per-item 1\u2013200000 \xB7 up to 25 filters \xB7 up to 100 IDs per request
4009
+
4010
+ FILTER FIELDS \u2014 document kind, as FIELD.OP=VALUE (or FIELD=VALUE for equality)
4011
+ id title author venue doi arxivId pmid
4012
+ publishedDate publishedYear sourceFamily sourceDomain
4013
+ annotated engaged lastEngagedAt
4014
+ Operators: eq \xB7 in \xB7 contains \xB7 prefix \xB7 gte \xB7 gt \xB7 lte \xB7 lt \xB7 between (which apply depends on the field).`;
4015
+ var HELP_PAGES = {
4016
+ ...HELP_TOPICS,
4017
+ reference: REFERENCE
4018
+ };
4019
+ var HELP_TOPIC_NAMES = Object.keys(HELP_PAGES);
4020
+ var HELP_COMMAND_NAMES = Object.keys(HELP_TOPICS);
4021
+ var HELP_TEXT = [GLOBAL_HELP, ...Object.values(HELP_PAGES)].join(
4022
+ "\n\n\n"
4023
+ );
4024
+ function colorizeHelp(text) {
4025
+ return text.replace(/^([A-Z][A-Z ]{2,})$/gm, "\x1B[1;36m$1\x1B[0m").replace(
4026
+ /(^|[\s([|/])(--[a-z0-9-]+|-[A-Za-z])\b/g,
4027
+ "$1\x1B[33m$2\x1B[0m"
4028
+ );
4029
+ }
4030
+ function renderHelp(command, style) {
4031
+ const help = command.type === "help" ? command : void 0;
4032
+ let body = GLOBAL_HELP;
4033
+ if (help?.all) {
4034
+ body = HELP_TEXT;
4035
+ } else if (help?.topic) {
4036
+ body = HELP_PAGES[help.topic] ?? GLOBAL_HELP;
4037
+ }
4038
+ return style ? colorizeHelp(body) : body;
4039
+ }
4040
+ var SUGGESTIBLE_COMMANDS = [
4041
+ "auth",
4042
+ "doctor",
4043
+ "capabilities",
4044
+ "tokens",
4045
+ "list",
4046
+ "search",
4047
+ "browse",
4048
+ "read",
4049
+ "version",
4050
+ "help"
4051
+ ];
4052
+ function levenshtein(a, b) {
4053
+ const rows = a.length + 1;
4054
+ const cols = b.length + 1;
4055
+ const dist = Array.from(
4056
+ { length: rows },
4057
+ () => new Array(cols).fill(0)
4058
+ );
4059
+ for (let i = 0; i < rows; i += 1) dist[i][0] = i;
4060
+ for (let j = 0; j < cols; j += 1) dist[0][j] = j;
4061
+ for (let i = 1; i < rows; i += 1) {
4062
+ for (let j = 1; j < cols; j += 1) {
4063
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
4064
+ dist[i][j] = Math.min(
4065
+ dist[i - 1][j] + 1,
4066
+ dist[i][j - 1] + 1,
4067
+ dist[i - 1][j - 1] + cost
4068
+ );
4069
+ }
4070
+ }
4071
+ return dist[rows - 1][cols - 1];
4072
+ }
4073
+ function suggestCommand(input) {
4074
+ let best = null;
4075
+ let bestDistance = Number.POSITIVE_INFINITY;
4076
+ for (const candidate of SUGGESTIBLE_COMMANDS) {
4077
+ const distance = levenshtein(input, candidate);
4078
+ if (distance < bestDistance) {
4079
+ bestDistance = distance;
4080
+ best = candidate;
4081
+ }
4082
+ }
4083
+ return best !== null && bestDistance <= 2 ? best : null;
4084
+ }
4085
+ function unknownCommandMessage(command) {
4086
+ const suggestion = suggestCommand(command);
4087
+ const guess = suggestion ? ` Did you mean "${suggestion}"?` : "";
4088
+ return `Unknown papiers command.${guess} Run papiers --help to see commands.`;
4089
+ }
3483
4090
  function parseCliArgs(argv) {
3484
4091
  const parsed = parseRawArgv(argv);
3485
4092
  const global = parseGlobalOptions(parsed);
3486
4093
  if (parsed.booleanFlags.has("version") && !parsed.help) {
3487
4094
  return { type: "version", ...global };
3488
4095
  }
3489
- if (parsed.help || parsed.positionals.length === 0) {
4096
+ const [command, ...rest] = parsed.positionals;
4097
+ if (parsed.help || command === "help" || parsed.positionals.length === 0) {
4098
+ if (parsed.booleanFlags.has("all")) {
4099
+ return { type: "help", all: true, ...global };
4100
+ }
4101
+ const topic = command === "help" ? rest[0] : command;
4102
+ if (topic && HELP_TOPIC_NAMES.includes(topic)) {
4103
+ return { type: "help", topic, ...global };
4104
+ }
3490
4105
  return { type: "help", ...global };
3491
4106
  }
3492
- const [command, ...rest] = parsed.positionals;
3493
4107
  switch (command) {
3494
- case "help":
3495
- case "--help":
3496
- return { type: "help", ...global };
3497
4108
  case "version":
3498
4109
  assertNoExtraPositionals(command, rest);
3499
4110
  return { type: "version", ...global };
@@ -3527,7 +4138,7 @@ function parseCliArgs(argv) {
3527
4138
  type: "list",
3528
4139
  ...global,
3529
4140
  idsOnly: listOutput.idsOnly,
3530
- ...parsePrettyOutput(parsed, listOutput.idsOnly) ? { pretty: true } : {},
4141
+ ...parsePrettyOutput(parsed, listOutput.anyIdsOutput) ? { pretty: true } : {},
3531
4142
  ...listOutput.idsNulDelimited ? { idsNulDelimited: true } : {},
3532
4143
  ...listOutput.docIdsOnly ? { docIdsOnly: true } : {},
3533
4144
  ...parsed.booleanFlags.has("input0") ? { inputNulDelimited: true } : {},
@@ -3537,16 +4148,23 @@ function parseCliArgs(argv) {
3537
4148
  }
3538
4149
  case "search": {
3539
4150
  assertAllowedFlags(parsed, SEARCH_VALUE_FLAGS, "search");
3540
- assertAllowedBooleanFlags(parsed, IDS_PIPELINE_BOOLEAN_FLAGS, "search");
4151
+ assertAllowedBooleanFlags(
4152
+ parsed,
4153
+ SEARCH_IDS_PIPELINE_BOOLEAN_FLAGS,
4154
+ "search"
4155
+ );
3541
4156
  const searchDocumentIds = parseDocumentIdFlagValues(parsed, "search");
3542
4157
  const searchOutput = parseIdsOutput(parsed, "search");
3543
4158
  return {
3544
4159
  type: "search",
3545
4160
  ...global,
3546
4161
  idsOnly: searchOutput.idsOnly,
3547
- ...parsePrettyOutput(parsed, searchOutput.idsOnly) ? { pretty: true } : {},
4162
+ ...parsePrettyOutput(parsed, searchOutput.anyIdsOutput) ? { pretty: true } : {},
3548
4163
  ...searchOutput.idsNulDelimited ? { idsNulDelimited: true } : {},
3549
4164
  ...searchOutput.docIdsOnly ? { docIdsOnly: true } : {},
4165
+ ...searchOutput.passageIdsOnly ? { passageIdsOnly: true } : {},
4166
+ ...searchOutput.highlightIdsOnly ? { highlightIdsOnly: true } : {},
4167
+ ...searchOutput.evidenceIdsOnly ? { evidenceIdsOnly: true } : {},
3550
4168
  ...parsed.booleanFlags.has("input0") ? { inputNulDelimited: true } : {},
3551
4169
  ...searchDocumentIds ? { documentIds: searchDocumentIds } : {},
3552
4170
  request: buildSearchRequest(rest, parsed, searchDocumentIds)
@@ -3554,7 +4172,11 @@ function parseCliArgs(argv) {
3554
4172
  }
3555
4173
  case "browse": {
3556
4174
  assertAllowedFlags(parsed, BROWSE_VALUE_FLAGS, "browse");
3557
- assertAllowedBooleanFlags(parsed, IDS_PIPELINE_BOOLEAN_FLAGS, "browse");
4175
+ assertAllowedBooleanFlags(
4176
+ parsed,
4177
+ SEARCH_IDS_PIPELINE_BOOLEAN_FLAGS,
4178
+ "browse"
4179
+ );
3558
4180
  if (parsed.booleanFlags.has("input0")) {
3559
4181
  throw new CliUsageError("--input0 is not supported for browse.");
3560
4182
  }
@@ -3563,9 +4185,12 @@ function parseCliArgs(argv) {
3563
4185
  type: "browse",
3564
4186
  ...global,
3565
4187
  idsOnly: browseOutput.idsOnly,
3566
- ...parsePrettyOutput(parsed, browseOutput.idsOnly) ? { pretty: true } : {},
4188
+ ...parsePrettyOutput(parsed, browseOutput.anyIdsOutput) ? { pretty: true } : {},
3567
4189
  ...browseOutput.idsNulDelimited ? { idsNulDelimited: true } : {},
3568
4190
  ...browseOutput.docIdsOnly ? { docIdsOnly: true } : {},
4191
+ ...browseOutput.passageIdsOnly ? { passageIdsOnly: true } : {},
4192
+ ...browseOutput.highlightIdsOnly ? { highlightIdsOnly: true } : {},
4193
+ ...browseOutput.evidenceIdsOnly ? { evidenceIdsOnly: true } : {},
3569
4194
  request: buildBrowseRequest(rest, parsed)
3570
4195
  };
3571
4196
  }
@@ -3584,7 +4209,7 @@ function parseCliArgs(argv) {
3584
4209
  };
3585
4210
  }
3586
4211
  default:
3587
- throw new CliUsageError("Unknown papiers command.");
4212
+ throw new CliUsageError(unknownCommandMessage(command));
3588
4213
  }
3589
4214
  }
3590
4215
  function argvWantsJson(argv) {
@@ -3594,9 +4219,9 @@ function parsePrettyOutput(parsed, idsOnly) {
3594
4219
  if (!parsed.booleanFlags.has("pretty")) {
3595
4220
  return false;
3596
4221
  }
3597
- if (idsOnly || parsed.json || parsed.booleanFlags.has("doc-ids")) {
4222
+ if (idsOnly || parsed.json) {
3598
4223
  throw new CliUsageError(
3599
- "--pretty cannot be combined with --json, --ids, --ids0, or --doc-ids."
4224
+ "--pretty cannot be combined with --json or ID projection flags."
3600
4225
  );
3601
4226
  }
3602
4227
  return true;
@@ -3636,12 +4261,6 @@ function parseAuthCommand(rest, parsed, global) {
3636
4261
  case "login": {
3637
4262
  assertAllowedBooleanFlags(parsed, AUTH_LOGIN_BOOLEAN_FLAGS, "auth login");
3638
4263
  assertNoExtraPositionals("auth login", extra);
3639
- const patStdinFlagCount = Number(parsed.booleanFlags.has("token-stdin")) + Number(parsed.booleanFlags.has("stdin"));
3640
- if (patStdinFlagCount !== 1) {
3641
- throw new CliUsageError(
3642
- "papiers auth login requires exactly one of --token-stdin or --stdin."
3643
- );
3644
- }
3645
4264
  return {
3646
4265
  type: "auth-login",
3647
4266
  ...global,
@@ -3683,7 +4302,7 @@ function buildReadRequest(parsed) {
3683
4302
  const ids = orderedReadIdValues(parsed);
3684
4303
  if (ids.length === 0) {
3685
4304
  throw new CliUsageError(
3686
- "papiers read requires at least one doc_*, pas_*, hl_*, cnv_*, or eq_* ID."
4305
+ "papiers read requires at least one public ID or papiers:// resource locator."
3687
4306
  );
3688
4307
  }
3689
4308
  if (ids.filter((id) => id === "@-").length > 1) {
@@ -3697,7 +4316,7 @@ function buildReadRequest(parsed) {
3697
4316
  const literalKinds = readKindsForLiteralIds(ids);
3698
4317
  if (literalKinds.length > 1) {
3699
4318
  throw new CliUsageError(
3700
- "papiers read IDs must all use the same public resource kind."
4319
+ "papiers read identifiers must all use the same resource kind."
3701
4320
  );
3702
4321
  }
3703
4322
  const inferredKind = literalKinds[0] ?? requestedKind ?? "document";
@@ -3713,9 +4332,9 @@ function buildReadRequest(parsed) {
3713
4332
  }
3714
4333
  const format = parseReadFormat(parsed, inferredKind);
3715
4334
  const cursor = singleFlagValue(parsed, "cursor");
3716
- if (cursor !== void 0 && (inferredKind !== "document" || ids.length !== 1 || ids.some((id) => isDocumentIdSource(id)) || !isDocumentPublicId(ids[0]))) {
4335
+ if (cursor !== void 0 && (inferredKind !== "document" && inferredKind !== "note" || ids.length !== 1 || ids.some((id) => isDocumentIdSource(id)) || readKindForIdentifier(ids[0]) !== inferredKind)) {
3717
4336
  throw new CliUsageError(
3718
- "papiers read --cursor requires exactly one literal doc_* ID."
4337
+ "papiers read --cursor requires exactly one literal document or note identifier."
3719
4338
  );
3720
4339
  }
3721
4340
  if (cursor !== void 0 && format !== "auto" && !DOCUMENT_CONTENT_READ_FORMATS.some((supported) => supported === format)) {
@@ -3792,104 +4411,65 @@ function buildSearchRequest(positionals, parsed, documentIds) {
3792
4411
  "Ranked public search cursors are not available yet."
3793
4412
  );
3794
4413
  }
3795
- if (isMultiSearchKindFlag(parsed)) {
3796
- return buildMultiKindSearchRequest(parsed, query, documentIds);
3797
- }
3798
- if (!parsed.flags.has("kind")) {
3799
- const defaulted = buildDefaultKindSearchRequest(parsed, query, documentIds);
3800
- if (defaulted) {
3801
- return defaulted;
3802
- }
3803
- }
3804
- const kind = parseListSearchKind(parsed, "search");
3805
- const since = parseSinceFlag(parsed, kind, "search");
3806
- if (kind !== "document") {
3807
- if (documentIds?.length && kind !== "highlight" && kind !== "passage") {
3808
- throw new CliUsageError(
3809
- "papiers search --document-id is supported only with --kind document, passage, or highlight."
3810
- );
3811
- }
3812
- assertNoNonDocumentListSearchFlag(parsed, "filter", "search", kind);
3813
- return pruneUndefined({
3814
- schemaVersion: PAPIERS_SCHEMA_VERSIONS.searchRequest,
3815
- query,
3816
- kinds: [kind],
3817
- queryIn: parseSearchQueryIn(parsed, kind),
3818
- documentIds: documentIds?.length ? documentIds : void 0,
3819
- since,
3820
- limit: parseLimit(parsed)
3821
- });
3822
- }
3823
- const filters = parsed.flags.get("filter")?.map((filter) => parseDocumentFilterExpression(filter));
3824
- assertFilterCount(filters, "search", documentIds ? 1 : 0);
3825
- return pruneUndefined({
3826
- schemaVersion: PAPIERS_SCHEMA_VERSIONS.searchRequest,
3827
- query,
3828
- kinds: ["document"],
3829
- queryIn: parseSearchQueryIn(parsed, kind),
3830
- filters,
3831
- limit: parseLimit(parsed)
3832
- });
3833
- }
3834
- function isMultiSearchKindFlag(parsed) {
3835
- return singleFlagValue(parsed, "kind")?.trim().toLowerCase() === "all";
3836
- }
3837
- function buildDefaultKindSearchRequest(parsed, query, documentIds) {
3838
- const queryIn = singleFlagValue(parsed, "query-in");
3839
- if (parsed.flags.has("filter") || documentIds?.length || queryIn === "title" || queryIn === "metadata") {
3840
- return null;
3841
- }
3842
- if (parsed.flags.has("since")) {
3843
- throw new CliUsageError(
3844
- "papiers search --since is supported only with --kind highlight or conversation."
3845
- );
3846
- }
3847
- if (queryIn === "latex") {
4414
+ if (parsed.flags.has("query-in")) {
3848
4415
  throw new CliUsageError(
3849
- "papiers search --query-in latex requires --kind equation."
4416
+ "papiers search no longer supports --query-in; use free-text query plus --filter FIELD.OP=VALUE for structured attributes."
3850
4417
  );
3851
4418
  }
3852
- if (queryIn !== void 0 && queryIn !== "content") {
4419
+ if (parsed.flags.has("since")) {
3853
4420
  throw new CliUsageError(
3854
- "papiers search supports --query-in title, metadata, content, or latex."
4421
+ "papiers search no longer supports --since; use --filter lastEngagedAt.gte=<when> for document recency."
3855
4422
  );
3856
4423
  }
4424
+ const kinds = parseSearchKindScopes(parsed);
4425
+ const filters = parsed.flags.get("filter")?.map((filter) => parseDocumentFilterExpression(filter));
4426
+ assertFilterCount(filters, "search");
3857
4427
  return pruneUndefined({
3858
4428
  schemaVersion: PAPIERS_SCHEMA_VERSIONS.searchRequest,
3859
4429
  query,
3860
- queryIn,
4430
+ kinds,
4431
+ filters,
3861
4432
  limit: parseLimit(parsed)
3862
4433
  });
3863
4434
  }
3864
- function buildMultiKindSearchRequest(parsed, query, documentIds) {
3865
- if (documentIds?.length) {
3866
- throw new CliUsageError(
3867
- "papiers search --document-id is not supported with --kind all; use --kind passage or --kind highlight."
3868
- );
4435
+ function parseSearchKindScopes(parsed) {
4436
+ const rawKinds = parsed.flags.get("kind")?.flatMap(splitCommaSeparatedValues);
4437
+ if (rawKinds === void 0) {
4438
+ return void 0;
3869
4439
  }
3870
- if (parsed.flags.has("since")) {
3871
- throw new CliUsageError(
3872
- "papiers search --since is not supported with --kind all; use --kind highlight or conversation."
3873
- );
4440
+ if (rawKinds.length === 0) {
4441
+ throw new CliUsageError("papiers search --kind requires a value.");
3874
4442
  }
3875
- if (parsed.flags.has("filter")) {
3876
- throw new CliUsageError(
3877
- "papiers search --filter is not supported with --kind all."
3878
- );
4443
+ const sawAll = rawKinds.some((kind) => kind.trim().toLowerCase() === "all");
4444
+ if (sawAll) {
4445
+ if (rawKinds.length > 1) {
4446
+ throw new CliUsageError(
4447
+ "papiers search --kind all cannot be combined with other --kind values."
4448
+ );
4449
+ }
4450
+ return void 0;
3879
4451
  }
3880
- const queryIn = singleFlagValue(parsed, "query-in");
3881
- if (queryIn !== void 0 && queryIn !== "content") {
3882
- throw new CliUsageError(
3883
- "papiers search --kind all supports --query-in content only."
3884
- );
4452
+ const kinds = [];
4453
+ for (const rawKind of rawKinds) {
4454
+ const kind = normalizeCliPublicKind(rawKind);
4455
+ if (!kind) {
4456
+ throw new CliUsageError(
4457
+ `papiers search supports --kind passage, highlight, conversation, equation, or note; received "${rawKind}".${didYouMeanKind(rawKind)}`
4458
+ );
4459
+ }
4460
+ if (kind === "document") {
4461
+ throw new CliUsageError(
4462
+ "papiers search no longer supports --kind document; omit --kind to search matching titles and content."
4463
+ );
4464
+ }
4465
+ if (!MULTI_SEARCH_KINDS.includes(kind)) {
4466
+ throw new CliUsageError(
4467
+ `papiers search supports --kind passage, highlight, conversation, equation, or note; received "${rawKind}".`
4468
+ );
4469
+ }
4470
+ kinds.push(kind);
3885
4471
  }
3886
- return pruneUndefined({
3887
- schemaVersion: PAPIERS_SCHEMA_VERSIONS.searchRequest,
3888
- query,
3889
- kinds: [...MULTI_SEARCH_KINDS],
3890
- queryIn: "content",
3891
- limit: parseLimit(parsed)
3892
- });
4472
+ return [...new Set(kinds)];
3893
4473
  }
3894
4474
  function parseSinceFlag(parsed, kind, command) {
3895
4475
  const value = singleFlagValue(parsed, "since");
@@ -3964,7 +4544,7 @@ function parseReadKind(parsed) {
3964
4544
  return normalized;
3965
4545
  }
3966
4546
  throw new CliUsageError(
3967
- `Public CLI read currently supports --kind document, passage, highlight, conversation, or equation only; received "${kind}". ${PUBLIC_KIND_ALIAS_HELP}`
4547
+ `Public CLI read currently supports --kind document, passage, highlight, conversation, equation, or note only; received "${kind}".${didYouMeanKind(kind)}`
3968
4548
  );
3969
4549
  }
3970
4550
  function readKindsForLiteralIds(ids) {
@@ -3973,10 +4553,10 @@ function readKindsForLiteralIds(ids) {
3973
4553
  if (isDocumentIdSource(id)) {
3974
4554
  continue;
3975
4555
  }
3976
- const kind = publicReadKindForId(id);
4556
+ const kind = readKindForIdentifier(id);
3977
4557
  if (!kind) {
3978
4558
  throw new CliUsageError(
3979
- "papiers read currently accepts only doc_*, pas_*, hl_*, cnv_*, or eq_* IDs."
4559
+ "papiers read accepts public IDs or papiers://note, highlight, item, and conversation locators."
3980
4560
  );
3981
4561
  }
3982
4562
  kinds.add(kind);
@@ -4023,13 +4603,25 @@ function parseIdsOutput(parsed, command) {
4023
4603
  const idsOnly = parsed.booleanFlags.has("ids");
4024
4604
  const idsNulDelimited = parsed.booleanFlags.has("ids0");
4025
4605
  const docIdsOnly = parsed.booleanFlags.has("doc-ids");
4026
- const selectedCount = Number(idsOnly) + Number(idsNulDelimited) + Number(docIdsOnly) + Number(parsed.json);
4606
+ const passageIdsOnly = parsed.booleanFlags.has("passage-ids");
4607
+ const highlightIdsOnly = parsed.booleanFlags.has("highlight-ids");
4608
+ const evidenceIdsOnly = parsed.booleanFlags.has("evidence-ids");
4609
+ const selectedCount = Number(idsOnly) + Number(idsNulDelimited) + Number(docIdsOnly) + Number(passageIdsOnly) + Number(highlightIdsOnly) + Number(evidenceIdsOnly) + Number(parsed.json);
4027
4610
  if (selectedCount > 1) {
4028
4611
  throw new CliUsageError(
4029
- `Use only one of --ids, --ids0, --doc-ids, or --json for papiers ${command}.`
4612
+ `Use only one of --ids, --ids0, --doc-ids, --passage-ids, --highlight-ids, --evidence-ids, or --json for papiers ${command}.`
4030
4613
  );
4031
4614
  }
4032
- return { idsOnly: idsOnly || idsNulDelimited, idsNulDelimited, docIdsOnly };
4615
+ const normalizedIdsOnly = idsOnly || idsNulDelimited;
4616
+ return {
4617
+ idsOnly: normalizedIdsOnly,
4618
+ idsNulDelimited,
4619
+ docIdsOnly,
4620
+ passageIdsOnly,
4621
+ highlightIdsOnly,
4622
+ evidenceIdsOnly,
4623
+ anyIdsOutput: normalizedIdsOnly || docIdsOnly || passageIdsOnly || highlightIdsOnly || evidenceIdsOnly
4624
+ };
4033
4625
  }
4034
4626
  function parseReadInclude(parsed) {
4035
4627
  const rawIncludes = parsed.flags.get("include")?.flatMap(splitCommaSeparatedValues);
@@ -4072,6 +4664,21 @@ function validateReadOptionsForKind(parsed, kind, format) {
4072
4664
  });
4073
4665
  return;
4074
4666
  }
4667
+ if (kind === "note") {
4668
+ if (format !== "auto" && !EXPECTED_NOTE_READ_FORMATS.some((supported) => supported === format)) {
4669
+ throw new CliUsageError(
4670
+ "Public note read supports --format markdown, plain, or auto."
4671
+ );
4672
+ }
4673
+ const includes = parseReadInclude(parsed);
4674
+ if (includes?.some((include) => include !== "metadata")) {
4675
+ throw new CliUsageError(
4676
+ "Public note read supports only --include metadata."
4677
+ );
4678
+ }
4679
+ rejectReadFlagsForKind(parsed, kind, ["pages"]);
4680
+ return;
4681
+ }
4075
4682
  validateMetadataReadOptionsForKind(parsed, kind, format, {
4076
4683
  supportedFormats: EXPECTED_EQUATION_READ_FORMATS
4077
4684
  });
@@ -4190,7 +4797,8 @@ function parseRawArgv(argv) {
4190
4797
  continue;
4191
4798
  }
4192
4799
  if (!VALUE_FLAGS.has(name)) {
4193
- throw new CliUsageError(`Unknown option --${name}.`);
4800
+ const hint = REMOVED_FLAG_HINTS[name] ?? "Run papiers --help to see options.";
4801
+ throw new CliUsageError(`Unknown option --${name}. ${hint}`);
4194
4802
  }
4195
4803
  const flagValue = value ?? argv[index + 1];
4196
4804
  if (flagValue === void 0 || flagValue.startsWith("--")) {
@@ -4221,14 +4829,9 @@ function splitLongFlag(arg) {
4221
4829
  };
4222
4830
  }
4223
4831
  function parseGlobalOptions(parsed) {
4224
- const apiBaseUrl = singleFlagValue(parsed, "api-base-url");
4225
- const apiUrlAlias = singleFlagValue(parsed, "api-url");
4226
- if (apiBaseUrl && apiUrlAlias) {
4227
- throw new CliUsageError("Use only one of --api-base-url or --api-url.");
4228
- }
4229
4832
  return {
4230
4833
  json: parsed.json,
4231
- apiBaseUrl: apiBaseUrl ?? apiUrlAlias
4834
+ apiBaseUrl: singleFlagValue(parsed, "api-url")
4232
4835
  };
4233
4836
  }
4234
4837
  function parseListSearchKind(parsed, command) {
@@ -4243,11 +4846,16 @@ function parseListSearchKind(parsed, command) {
4243
4846
  );
4244
4847
  }
4245
4848
  throw new CliUsageError(
4246
- `papiers ${command} supports --kind document, passage, highlight, conversation, or equation; received "${kind}". ${PUBLIC_KIND_ALIAS_HELP}`
4849
+ `papiers ${command} supports --kind document, passage, highlight, conversation, equation, or note; received "${kind}".${didYouMeanKind(kind)}`
4247
4850
  );
4248
4851
  }
4249
4852
  function normalizeCliPublicKind(value) {
4250
- return PUBLIC_KIND_ALIASES.get(value.trim().toLowerCase()) ?? null;
4853
+ const normalized = value.trim().toLowerCase();
4854
+ return PUBLIC_KINDS.includes(normalized) ? normalized : null;
4855
+ }
4856
+ function didYouMeanKind(value) {
4857
+ const suggestion = FORMER_KIND_ALIASES.get(value.trim().toLowerCase());
4858
+ return suggestion ? ` Did you mean "${suggestion}"?` : "";
4251
4859
  }
4252
4860
  function assertNoNonDocumentListSearchFlag(parsed, flag, command, kind) {
4253
4861
  if (parsed.flags.has(flag)) {
@@ -4300,34 +4908,6 @@ function parseLimit(parsed) {
4300
4908
  }
4301
4909
  return limit;
4302
4910
  }
4303
- function parseSearchQueryIn(parsed, kind) {
4304
- const queryIn = singleFlagValue(parsed, "query-in");
4305
- if (kind === "document") {
4306
- if (queryIn === void 0) {
4307
- return void 0;
4308
- }
4309
- if (EXPECTED_DOCUMENT_QUERY_IN.some((supported2) => supported2 === queryIn)) {
4310
- return queryIn;
4311
- }
4312
- throw new CliUsageError(
4313
- "Public document search supports --query-in title or --query-in metadata."
4314
- );
4315
- }
4316
- const defaultsByKind = {
4317
- passage: EXPECTED_CONTENT_QUERY_IN[0],
4318
- highlight: EXPECTED_CONTENT_QUERY_IN[0],
4319
- conversation: EXPECTED_CONTENT_QUERY_IN[0],
4320
- equation: EXPECTED_EQUATION_QUERY_IN[0]
4321
- };
4322
- const resolved = queryIn ?? defaultsByKind[kind];
4323
- const supported = kind === "equation" ? EXPECTED_EQUATION_QUERY_IN : EXPECTED_CONTENT_QUERY_IN;
4324
- if (supported.some((value) => value === resolved)) {
4325
- return resolved;
4326
- }
4327
- throw new CliUsageError(
4328
- kind === "equation" ? "Public equation search supports --query-in latex only." : `Public ${kind} search supports --query-in content only.`
4329
- );
4330
- }
4331
4911
  function assertFilterCount(filters, command, reservedFilterCount = 0) {
4332
4912
  const totalFilterCount = (filters?.length ?? 0) + reservedFilterCount;
4333
4913
  if (totalFilterCount > DOCUMENT_MAX_FILTERS) {
@@ -4511,26 +5091,7 @@ function assertSupportedDocumentCapabilities(value) {
4511
5091
  assertSupportedSearchResultsBrowse(capabilities.searchResultsBrowse);
4512
5092
  const queryIn = assertRecord2("capabilities queryIn", capabilities.queryIn);
4513
5093
  assertRecordKeysSubset("capabilities queryIn", queryIn, PUBLIC_KINDS);
4514
- const expectedQueryInByKind = {
4515
- document: EXPECTED_DOCUMENT_QUERY_IN
4516
- };
4517
- if (searchKinds.includes("passage")) {
4518
- expectedQueryInByKind.passage = EXPECTED_CONTENT_QUERY_IN;
4519
- }
4520
- if (searchKinds.includes("highlight")) {
4521
- expectedQueryInByKind.highlight = EXPECTED_CONTENT_QUERY_IN;
4522
- }
4523
- if (searchKinds.includes("conversation")) {
4524
- expectedQueryInByKind.conversation = EXPECTED_CONTENT_QUERY_IN;
4525
- }
4526
- if (searchKinds.includes("equation")) {
4527
- expectedQueryInByKind.equation = EXPECTED_EQUATION_QUERY_IN;
4528
- }
4529
- assertPublicKindStringArrayRecord(
4530
- "capabilities queryIn",
4531
- queryIn,
4532
- expectedQueryInByKind
4533
- );
5094
+ assertPublicKindStringArrayRecord("capabilities queryIn", queryIn, {});
4534
5095
  const readFormats = assertStringArray(
4535
5096
  "capabilities readFormats",
4536
5097
  capabilities.readFormats
@@ -4592,6 +5153,13 @@ function assertSupportedDocumentCapabilities(value) {
4592
5153
  EXPECTED_EQUATION_READ_FORMATS
4593
5154
  );
4594
5155
  continue;
5156
+ case "note":
5157
+ assertOptionalReadFormats(
5158
+ `capabilities readFormatsByKind.${kind}`,
5159
+ actualFormats,
5160
+ EXPECTED_NOTE_READ_FORMATS
5161
+ );
5162
+ continue;
4595
5163
  default:
4596
5164
  assertSameStringSet(
4597
5165
  `capabilities readFormatsByKind.${kind}`,
@@ -4619,7 +5187,11 @@ function assertSupportedDocumentCapabilities(value) {
4619
5187
  `capabilities filters.${kind}`,
4620
5188
  filters[kind]
4621
5189
  );
4622
- assertSameStringSet(`capabilities filters.${kind}`, actualFilters, []);
5190
+ assertSameStringSet(
5191
+ `capabilities filters.${kind}`,
5192
+ actualFilters,
5193
+ kind === "conversation" ? EXPECTED_CONVERSATION_FILTERS : []
5194
+ );
4623
5195
  }
4624
5196
  const operatorsByField = assertRecord2(
4625
5197
  "capabilities operatorsByField",
@@ -4723,12 +5295,9 @@ function assertPublicKindStringArrayRecord(label, record, expectedByKind) {
4723
5295
  }
4724
5296
  }
4725
5297
  function assertSupportedSearchKinds(searchKinds) {
4726
- if (!searchKinds.includes("document")) {
4727
- throw new Error("capabilities searchKinds did not include document.");
4728
- }
4729
5298
  const seen = /* @__PURE__ */ new Set();
4730
5299
  for (const kind of searchKinds) {
4731
- if (seen.has(kind) || !PUBLIC_KINDS.includes(kind)) {
5300
+ if (seen.has(kind) || kind === "document" || !PUBLIC_KINDS.includes(kind)) {
4732
5301
  throw new Error(
4733
5302
  `capabilities searchKinds advertised unsupported kind: ${kind}.`
4734
5303
  );
@@ -4950,9 +5519,16 @@ function isPublicNotReadyReason(reason, reasons) {
4950
5519
  }
4951
5520
 
4952
5521
  // src/pretty.ts
5522
+ function warningLines(warnings) {
5523
+ if (!warnings?.length) {
5524
+ return [];
5525
+ }
5526
+ return warnings.map((warning) => warning.message?.trim()).filter((message) => Boolean(message)).map((message) => `! ${message}`);
5527
+ }
4953
5528
  function renderPrettyPageOutput(response) {
4954
5529
  const lines = [];
4955
- for (const item of response.items) {
5530
+ const entries = response.results ?? response.items ?? [];
5531
+ for (const item of entries) {
4956
5532
  const rank = item.rank !== void 0 ? `${item.rank}. ` : "";
4957
5533
  const title = item.title?.trim() ? ` ${item.title.trim()}` : "";
4958
5534
  lines.push(`${rank}${item.id} [${item.kind}]${title}`);
@@ -4960,21 +5536,36 @@ function renderPrettyPageOutput(response) {
4960
5536
  if (excerpt?.trim()) {
4961
5537
  lines.push(` ${excerpt.trim()}`);
4962
5538
  }
5539
+ if (item.evidence?.length) {
5540
+ for (const evidence of item.evidence) {
5541
+ const evidenceRank = evidence.rank !== void 0 ? `${evidence.rank}. ` : "";
5542
+ lines.push(` - ${evidenceRank}${evidence.id} [${evidence.kind}]`);
5543
+ if (evidence.snippet?.trim()) {
5544
+ lines.push(` ${evidence.snippet.trim()}`);
5545
+ }
5546
+ }
5547
+ if (typeof item.evidenceTotal === "number" && item.evidenceTotal > item.evidence.length) {
5548
+ lines.push(
5549
+ ` + ${item.evidenceTotal - item.evidence.length} more evidence item${item.evidenceTotal - item.evidence.length === 1 ? "" : "s"}`
5550
+ );
5551
+ }
5552
+ }
4963
5553
  }
4964
5554
  if (lines.length === 0) {
4965
5555
  lines.push("(no items)");
4966
5556
  }
4967
- const itemCount = response.items.length;
5557
+ const itemCount = entries.length;
4968
5558
  lines.push(
4969
5559
  `(${itemCount} item${itemCount === 1 ? "" : "s"}; ${pagingSummary(response)})`
4970
5560
  );
5561
+ lines.push(...warningLines(response.warnings));
4971
5562
  return `${lines.join("\n")}
4972
5563
  `;
4973
5564
  }
4974
5565
  function pagingSummary(response) {
4975
5566
  const ranking = response.ranking;
4976
5567
  if (ranking) {
4977
- const nextOffset = ranking.nextOffset !== void 0 ? ranking.nextOffset : ranking.totalCount > response.items.length ? response.items.length : null;
5568
+ const nextOffset = ranking.nextOffset !== void 0 ? ranking.nextOffset : ranking.totalCount > (response.results ?? response.items ?? []).length ? (response.results ?? response.items ?? []).length : null;
4978
5569
  return nextOffset === null ? `ranking exhausted at ${ranking.totalCount}` : `${ranking.totalCount} ranked in total \u2014 papiers browse ${ranking.searchId} --offset ${nextOffset}`;
4979
5570
  }
4980
5571
  return response.page.nextCursor ? "more available \u2014 rerun with --json and pass page.nextCursor via --cursor" : "no more pages";
@@ -5054,7 +5645,9 @@ async function runCli(argv, dependencies = {}) {
5054
5645
  const command = parseCliArgs(argv);
5055
5646
  jsonOutput = command.json;
5056
5647
  if (command.type === "help") {
5057
- stdout.write(HELP_TEXT);
5648
+ const styled = dependencies.stdout === void 0 && process.stdout.isTTY === true && process.env.NO_COLOR === void 0;
5649
+ stdout.write(`${renderHelp(command, styled)}
5650
+ `);
5058
5651
  return 0;
5059
5652
  }
5060
5653
  if (command.type === "version") {
@@ -5126,12 +5719,27 @@ async function runCli(argv, dependencies = {}) {
5126
5719
  json: command.json,
5127
5720
  stdout
5128
5721
  });
5129
- case "capabilities":
5130
- writeJson(
5131
- stdout,
5132
- await getCapabilities(config, resolveFetch(dependencies.fetch))
5722
+ case "capabilities": {
5723
+ const capabilities = await getCapabilities(
5724
+ config,
5725
+ resolveFetch(dependencies.fetch)
5726
+ );
5727
+ if (command.json) {
5728
+ writeJson(stdout, capabilities);
5729
+ return 0;
5730
+ }
5731
+ const caps = capabilities;
5732
+ stdout.write(
5733
+ `${redactSensitiveText(
5734
+ [
5735
+ `Validation: ok requestId=${String(caps.requestId ?? "")}`,
5736
+ ...formatCapabilityLines(caps)
5737
+ ].join("\n")
5738
+ )}
5739
+ `
5133
5740
  );
5134
5741
  return 0;
5742
+ }
5135
5743
  case "tokens-list":
5136
5744
  writeTokenListOutput(
5137
5745
  stdout,
@@ -5198,11 +5806,17 @@ async function runCli(argv, dependencies = {}) {
5198
5806
  request
5199
5807
  );
5200
5808
  if (command.idsOnly) {
5201
- writeIds(stdout, response.items, {
5809
+ writeIds(stdout, response.results, {
5202
5810
  nulDelimited: command.idsNulDelimited === true
5203
5811
  });
5204
5812
  } else if (command.docIdsOnly) {
5205
- writeParentDocumentIds(stdout, response.items);
5813
+ writeParentDocumentIds(stdout, response.results);
5814
+ } else if (command.passageIdsOnly) {
5815
+ writeEvidenceIds(stdout, response.results, "passage");
5816
+ } else if (command.highlightIdsOnly) {
5817
+ writeEvidenceIds(stdout, response.results, "highlight");
5818
+ } else if (command.evidenceIdsOnly) {
5819
+ writeEvidenceIds(stdout, response.results);
5206
5820
  } else if (command.pretty) {
5207
5821
  stdout.write(redactSensitiveText(renderPrettyPageOutput(response)));
5208
5822
  } else {
@@ -5218,11 +5832,17 @@ async function runCli(argv, dependencies = {}) {
5218
5832
  command.request
5219
5833
  );
5220
5834
  if (command.idsOnly) {
5221
- writeIds(stdout, response.items, {
5835
+ writeIds(stdout, response.results, {
5222
5836
  nulDelimited: command.idsNulDelimited === true
5223
5837
  });
5224
5838
  } else if (command.docIdsOnly) {
5225
- writeParentDocumentIds(stdout, response.items);
5839
+ writeParentDocumentIds(stdout, response.results);
5840
+ } else if (command.passageIdsOnly) {
5841
+ writeEvidenceIds(stdout, response.results, "passage");
5842
+ } else if (command.highlightIdsOnly) {
5843
+ writeEvidenceIds(stdout, response.results, "highlight");
5844
+ } else if (command.evidenceIdsOnly) {
5845
+ writeEvidenceIds(stdout, response.results);
5226
5846
  } else if (command.pretty) {
5227
5847
  stdout.write(redactSensitiveText(renderPrettyPageOutput(response)));
5228
5848
  } else {
@@ -5280,16 +5900,13 @@ async function runAuthLogin(params) {
5280
5900
  }
5281
5901
  assertSafeBearerApiBaseUrl(params.config.apiBaseUrl);
5282
5902
  const token = await loginWithPatStdin(params);
5283
- const validationConfig = cliConfigWithToken(
5284
- params.config,
5285
- token,
5286
- "stdin"
5903
+ const validationConfig = cliConfigWithToken(params.config, token, "stdin");
5904
+ const capabilities = await getCapabilities(
5905
+ validationConfig,
5906
+ params.fetchImpl
5287
5907
  );
5288
- let capabilities;
5289
- capabilities = await getCapabilities(validationConfig, params.fetchImpl);
5290
5908
  assertCliSupportedCapabilities(capabilities);
5291
- let update;
5292
- update = writeLocalAuthToken({
5909
+ const update = writeLocalAuthToken({
5293
5910
  root: params.envRoot,
5294
5911
  token,
5295
5912
  apiBaseUrl: params.config.apiBaseUrl
@@ -5351,6 +5968,7 @@ function writeAuthLoginOutput(stdout, output, json) {
5351
5968
  return;
5352
5969
  }
5353
5970
  stdout.write("Signed in to Papiers.\n");
5971
+ stdout.write("Next, try: papiers list --limit 5 --pretty\n");
5354
5972
  }
5355
5973
  function writeAuthLogoutOutput(stdout, output, json) {
5356
5974
  if (json) {
@@ -5369,6 +5987,14 @@ function writeAuthLogoutOutput(stdout, output, json) {
5369
5987
  stdout.write(`${redactSensitiveText(lines.join("\n"))}
5370
5988
  `);
5371
5989
  }
5990
+ function formatCapabilityLines(capabilities) {
5991
+ return [
5992
+ `Scopes: ${formatUnknownList(capabilities.scopes)}`,
5993
+ `Search kinds: ${formatUnknownList(capabilities.searchKinds)}`,
5994
+ `Read formats: ${formatUnknownList(capabilities.readFormats)}`,
5995
+ `Not ready: ${formatNotReady(capabilities.notReady)}`
5996
+ ];
5997
+ }
5372
5998
  function writeAuthInspectOutput(stdout, output, json) {
5373
5999
  if (json) {
5374
6000
  writeJson(stdout, output);
@@ -5384,10 +6010,7 @@ function writeAuthInspectOutput(stdout, output, json) {
5384
6010
  `Account: userId=${String(
5385
6011
  capabilities.account?.userId ?? "unknown"
5386
6012
  )} profile=${String(capabilities.account?.profile ?? "unknown")}`,
5387
- `Scopes: ${formatUnknownList(capabilities.scopes)}`,
5388
- `Search kinds: ${formatUnknownList(capabilities.searchKinds)}`,
5389
- `Read formats: ${formatUnknownList(capabilities.readFormats)}`,
5390
- `Not ready: ${formatNotReady(capabilities.notReady)}`
6013
+ ...formatCapabilityLines(capabilities)
5391
6014
  ].join("\n")
5392
6015
  )}
5393
6016
  `
@@ -5543,13 +6166,13 @@ function normalizeStdinToken(rawInput) {
5543
6166
  const parts = rawInput.trim().split(/\s+/u).filter(Boolean);
5544
6167
  if (parts.length !== 1) {
5545
6168
  throw new CliUsageError(
5546
- "Token stdin must contain exactly one Papiers API token."
6169
+ "Token stdin must contain exactly one Papiers API token (starting with pap_live_), with no surrounding whitespace."
5547
6170
  );
5548
6171
  }
5549
6172
  const token = parts[0];
5550
6173
  if (!isPapiersPat(token)) {
5551
6174
  throw new CliUsageError(
5552
- "Token stdin did not contain a valid Papiers API token."
6175
+ "Token stdin did not contain a valid Papiers API token; expected one starting with pap_live_."
5553
6176
  );
5554
6177
  }
5555
6178
  return token;
@@ -5698,7 +6321,8 @@ function writeParentDocumentIds(stdout, items) {
5698
6321
  const seen = /* @__PURE__ */ new Set();
5699
6322
  const ids = [];
5700
6323
  for (const item of items) {
5701
- const docId = item.kind === "document" ? item.id : item.documentId;
6324
+ const metadataDocumentId = item.kind === "note" && typeof item.metadata === "object" && item.metadata !== null && !Array.isArray(item.metadata) ? item.metadata.documentId : void 0;
6325
+ const docId = item.kind === "document" ? item.id : item.documentId ?? metadataDocumentId;
5702
6326
  if (typeof docId !== "string" || !docId) {
5703
6327
  continue;
5704
6328
  }
@@ -5715,6 +6339,27 @@ function writeParentDocumentIds(stdout, items) {
5715
6339
  stdout.write(ids.length === 0 ? "" : `${ids.join("\n")}
5716
6340
  `);
5717
6341
  }
6342
+ function writeEvidenceIds(stdout, results, kind) {
6343
+ const ids = [];
6344
+ for (const result of results) {
6345
+ if (result.kind !== "document") {
6346
+ continue;
6347
+ }
6348
+ for (const evidence of result.evidence) {
6349
+ if (kind !== void 0 && evidence.kind !== kind) {
6350
+ continue;
6351
+ }
6352
+ if (publicReadKindForId(evidence.id) !== evidence.kind) {
6353
+ throw new Error(
6354
+ "Papiers API returned an evidence id that did not match its kind in evidence ID output."
6355
+ );
6356
+ }
6357
+ ids.push(evidence.id);
6358
+ }
6359
+ }
6360
+ stdout.write(ids.length === 0 ? "" : `${ids.join("\n")}
6361
+ `);
6362
+ }
5718
6363
  async function expandListDocumentIdRequest(request, documentIds, input) {
5719
6364
  const ids = await expandDocumentIdPlaceholders(documentIds, "list", input);
5720
6365
  if (!ids?.length) {
@@ -5940,23 +6585,23 @@ function validateReadIds(ids, context, sourceLabel) {
5940
6585
  }
5941
6586
  const kinds = /* @__PURE__ */ new Set();
5942
6587
  for (const [index, id] of ids.entries()) {
5943
- const kind = publicReadKindForId(id);
6588
+ const kind = readKindForIdentifier(id);
5944
6589
  if (!kind) {
5945
6590
  throw new CliUsageError(
5946
- `${context} ${sourceLabel} item ${index + 1} was not a doc_*, pas_*, hl_*, cnv_*, or eq_* ID.`
6591
+ `${context} ${sourceLabel} item ${index + 1} was not a public ID or papiers:// resource locator.`
5947
6592
  );
5948
6593
  }
5949
6594
  kinds.add(kind);
5950
6595
  }
5951
6596
  if (kinds.size > 1) {
5952
6597
  throw new CliUsageError(
5953
- `${context} ${sourceLabel} IDs must all use the same public resource kind.`
6598
+ `${context} ${sourceLabel} identifiers must all use the same resource kind.`
5954
6599
  );
5955
6600
  }
5956
6601
  return ids;
5957
6602
  }
5958
6603
  function validateExpandedReadRequest(request, ids, requestedReadKind) {
5959
- const kind = publicReadKindForId(ids[0] ?? "");
6604
+ const kind = readKindForIdentifier(ids[0] ?? "");
5960
6605
  if (requestedReadKind && kind && requestedReadKind !== kind) {
5961
6606
  throw new CliUsageError(
5962
6607
  `papiers read --kind ${requestedReadKind} does not match ${kind} IDs.`
@@ -5987,6 +6632,25 @@ function validateExpandedReadRequest(request, ids, requestedReadKind) {
5987
6632
  }
5988
6633
  if (kind === "equation") {
5989
6634
  validateMetadataOnlyReadRequest(request, kind);
6635
+ return;
6636
+ }
6637
+ if (kind === "note") {
6638
+ validateNoteReadRequest(request);
6639
+ }
6640
+ }
6641
+ function validateNoteReadRequest(request) {
6642
+ if (request.format !== void 0 && request.format !== "auto" && request.format !== "markdown" && request.format !== "plain") {
6643
+ throw new CliUsageError(
6644
+ "Public note read supports --format markdown, plain, or auto."
6645
+ );
6646
+ }
6647
+ if (request.include?.some((include) => include !== "metadata")) {
6648
+ throw new CliUsageError(
6649
+ "Public note read supports only --include metadata."
6650
+ );
6651
+ }
6652
+ if (request.scope?.pages || request.scope?.sectionId) {
6653
+ throw new CliUsageError("Public note read does not support --pages.");
5990
6654
  }
5991
6655
  }
5992
6656
  function validateContentCapableReadRequest(request, kind) {
@@ -6036,7 +6700,7 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6036
6700
  }
6037
6701
  if (input.startsWith("[")) {
6038
6702
  throw new CliUsageError(
6039
- `${context} JSON ${sourceLabel} must be a Papiers envelope with schemaVersion and items[].id.`
6703
+ `${context} JSON ${sourceLabel} must be a Papiers envelope with schemaVersion and items[].id or results[].id.`
6040
6704
  );
6041
6705
  }
6042
6706
  let parsed;
@@ -6049,7 +6713,7 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6049
6713
  }
6050
6714
  if (!parsed || typeof parsed !== "object") {
6051
6715
  throw new CliUsageError(
6052
- `${context} JSON ${sourceLabel} must be a Papiers envelope with schemaVersion and items[].id.`
6716
+ `${context} JSON ${sourceLabel} must be a Papiers envelope with schemaVersion and items[].id or results[].id.`
6053
6717
  );
6054
6718
  }
6055
6719
  const envelope = parsed;
@@ -6062,7 +6726,7 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6062
6726
  if (envelope.schemaVersion === PAPIERS_SCHEMA_VERSIONS.list) {
6063
6727
  assertPapiersListResponse(`${context} list envelope`, parsed);
6064
6728
  } else if (envelope.schemaVersion === PAPIERS_SCHEMA_VERSIONS.search) {
6065
- assertPapiersSearchResponse(`${context} search envelope`, parsed);
6729
+ validateSearchPipelineEnvelope(`${context} search envelope`, parsed);
6066
6730
  } else {
6067
6731
  assertPapiersReadPipelineEnvelope(`${context} read envelope`, parsed);
6068
6732
  }
@@ -6074,10 +6738,10 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6074
6738
  }
6075
6739
  throw error;
6076
6740
  }
6077
- const items = envelope.items;
6741
+ const items = envelope.schemaVersion === PAPIERS_SCHEMA_VERSIONS.search ? envelope.results : envelope.items;
6078
6742
  if (!Array.isArray(items)) {
6079
6743
  throw new CliUsageError(
6080
- `${context} JSON ${sourceLabel} must be a Papiers envelope with items[].id.`
6744
+ `${context} JSON ${sourceLabel} must be a Papiers envelope with items[].id or results[].id.`
6081
6745
  );
6082
6746
  }
6083
6747
  return items.map((item, index) => {
@@ -6093,15 +6757,28 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6093
6757
  );
6094
6758
  }
6095
6759
  const kind = item.kind;
6096
- const idKind = publicReadKindForId(id);
6760
+ const idKind = envelope.schemaVersion === PAPIERS_SCHEMA_VERSIONS.read ? readKindForIdentifier(id) : publicReadKindForId(id);
6097
6761
  if (kind !== idKind) {
6098
6762
  throw new CliUsageError(
6099
- `${context} JSON item ${index} kind did not match its public id.`
6763
+ `${context} JSON item ${index} kind did not match its identifier.`
6100
6764
  );
6101
6765
  }
6102
6766
  return id;
6103
6767
  });
6104
6768
  }
6769
+ function validateSearchPipelineEnvelope(label, parsed) {
6770
+ const envelope = parsed;
6771
+ const ranking = envelope.ranking && typeof envelope.ranking === "object" && !Array.isArray(envelope.ranking) ? envelope.ranking : null;
6772
+ if (ranking && ("offset" in ranking || "nextOffset" in ranking)) {
6773
+ assertPapiersSearchResultsResponse(label, parsed, {
6774
+ schemaVersion: PAPIERS_SCHEMA_VERSIONS.searchResultsRequest,
6775
+ searchId: typeof ranking.searchId === "string" ? ranking.searchId : "",
6776
+ offset: typeof ranking.offset === "number" ? ranking.offset : void 0
6777
+ });
6778
+ return;
6779
+ }
6780
+ assertPapiersSearchResponse(label, parsed);
6781
+ }
6105
6782
  function parseTextIds(input) {
6106
6783
  return input.split(/[\s,]+/u).filter(Boolean);
6107
6784
  }
@@ -6120,9 +6797,20 @@ function writeCliError(error, params) {
6120
6797
  `${redactSensitiveText(serialized.body.error.message)}
6121
6798
  `
6122
6799
  );
6800
+ const hint = humanErrorHint(error);
6801
+ if (hint) {
6802
+ params.stderr.write(`${hint}
6803
+ `);
6804
+ }
6123
6805
  }
6124
6806
  return serialized.exitCode;
6125
6807
  }
6808
+ function humanErrorHint(error) {
6809
+ if (error instanceof PapiersCliApiError && error.status === 401) {
6810
+ return "Your token may be expired or revoked. Run papiers auth login --force to replace it.";
6811
+ }
6812
+ return null;
6813
+ }
6126
6814
  function serializeCliError(error) {
6127
6815
  if (error instanceof PapiersCliApiError) {
6128
6816
  return {