papiers 0.1.2 → 0.1.3

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
@@ -420,16 +420,6 @@ var PUBLIC_KINDS = [
420
420
  "conversation",
421
421
  "equation"
422
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"
432
- ];
433
423
  var EXPECTED_DOCUMENT_READ_FORMATS = [
434
424
  "metadata",
435
425
  "markdown",
@@ -478,10 +468,13 @@ var EXPECTED_DOCUMENT_FILTERS = [
478
468
  "engaged",
479
469
  "lastEngagedAt"
480
470
  ];
471
+ var EXPECTED_CONVERSATION_FILTERS = [
472
+ "lastEngagedAt"
473
+ ];
481
474
  var EXPECTED_DOCUMENT_FILTER_OPERATORS = {
482
475
  id: ["eq", "in"],
483
476
  title: ["eq", "contains", "prefix", "in"],
484
- author: ["eq"],
477
+ author: ["eq", "contains"],
485
478
  venue: ["eq", "contains", "prefix", "in"],
486
479
  publishedDate: ["eq", "gte", "gt", "lte", "lt", "between"],
487
480
  publishedYear: ["eq", "gte", "gt", "lte", "lt", "between", "in"],
@@ -598,6 +591,25 @@ var PAPIERS_SEARCH_QUALITY_SIGNALS = [
598
591
  "low",
599
592
  "empty"
600
593
  ];
594
+ var PAPIERS_SEARCH_RESULT_KINDS = [
595
+ "document",
596
+ "conversation"
597
+ ];
598
+ var PAPIERS_SEARCH_EVIDENCE_KINDS = [
599
+ "passage",
600
+ "highlight",
601
+ "equation"
602
+ ];
603
+ var PAPIERS_SEARCH_MATCH_REASONS = [
604
+ "title",
605
+ "doi",
606
+ "arxivId",
607
+ "pmid",
608
+ "passage",
609
+ "highlight",
610
+ "equation",
611
+ "conversation"
612
+ ];
601
613
  var PAPIERS_PUBLIC_NOT_READY_REASONS_BY_FEATURE = {
602
614
  document_content_read: ["explicit_enablement_required"],
603
615
  non_document_kinds: ["public_id_projection_not_ready"],
@@ -613,7 +625,10 @@ var PAPIERS_PUBLIC_NOT_READY_REASONS_BY_FEATURE = {
613
625
  read_section_scopes: ["section_scoped_read_not_ready"],
614
626
  read_context: ["read_context_not_ready"],
615
627
  ranked_search_cursors: ["search_sessions_not_ready"],
616
- search_results_browse: ["content_search_not_ready"]
628
+ search_results_browse: [
629
+ "content_search_not_ready",
630
+ "search_sessions_not_ready"
631
+ ]
617
632
  };
618
633
  var PAPIERS_PUBLIC_ERROR_CODES = [
619
634
  "api_disabled",
@@ -746,7 +761,7 @@ var SEARCH_RESPONSE_KEYS = /* @__PURE__ */ new Set([
746
761
  "requestId",
747
762
  "query",
748
763
  "quality",
749
- "items",
764
+ "results",
750
765
  "page",
751
766
  "ranking",
752
767
  "warnings"
@@ -760,6 +775,9 @@ var SEARCH_RANKING_KEYS = /* @__PURE__ */ new Set([
760
775
  ]);
761
776
  var SEARCH_QUALITY_KEYS = /* @__PURE__ */ new Set(["signal", "suggestions"]);
762
777
  var SEARCH_QUALITY_SIGNALS = new Set(PAPIERS_SEARCH_QUALITY_SIGNALS);
778
+ var SEARCH_RESULT_KINDS = new Set(PAPIERS_SEARCH_RESULT_KINDS);
779
+ var SEARCH_EVIDENCE_KINDS = new Set(PAPIERS_SEARCH_EVIDENCE_KINDS);
780
+ var SEARCH_MATCH_REASONS = new Set(PAPIERS_SEARCH_MATCH_REASONS);
763
781
  var PUBLIC_WARNING_KEYS = /* @__PURE__ */ new Set(["code", "message"]);
764
782
  var DOCUMENT_SEARCH_WARNING_CODE = "metadata_only_search";
765
783
  var DOCUMENT_SEARCH_WARNING_MESSAGE = "Public document search currently searches catalog title and metadata, not document body content.";
@@ -821,6 +839,32 @@ var SEARCH_DOCUMENT_ITEM_KEYS = /* @__PURE__ */ new Set([
821
839
  "metadata",
822
840
  "updatedAt"
823
841
  ]);
842
+ var SEARCH_RESULT_BASE_KEYS = [
843
+ "id",
844
+ "kind",
845
+ "rank",
846
+ "score",
847
+ "matchedBy",
848
+ "title"
849
+ ];
850
+ var SEARCH_DOCUMENT_RESULT_KEYS = /* @__PURE__ */ new Set([
851
+ ...SEARCH_RESULT_BASE_KEYS,
852
+ "source",
853
+ "metadata",
854
+ "evidence",
855
+ "evidenceTotal"
856
+ ]);
857
+ var SEARCH_CONVERSATION_RESULT_KEYS = /* @__PURE__ */ new Set([
858
+ ...SEARCH_RESULT_BASE_KEYS,
859
+ "snippet"
860
+ ]);
861
+ var SEARCH_EVIDENCE_KEYS = /* @__PURE__ */ new Set([
862
+ "kind",
863
+ "id",
864
+ "rank",
865
+ "snippet",
866
+ "score"
867
+ ]);
824
868
  var READ_SUCCESS_DOCUMENT_ITEM_KEYS = /* @__PURE__ */ new Set([
825
869
  "id",
826
870
  "kind",
@@ -1050,17 +1094,12 @@ function assertPapiersSearchResponse(label, body, expectedQuery, options = {}) {
1050
1094
  `${label} did not echo the requested query.`
1051
1095
  );
1052
1096
  }
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) {
1097
+ const itemCount = validateSearchResults(label, value.results, options);
1098
+ const searchResults = value.results;
1099
+ for (const [index, result] of searchResults.entries()) {
1100
+ if (result.rank !== index + 1) {
1062
1101
  throw new PapiersCliResponseValidationError(
1063
- `${label} item ${index} rank was not contiguous from 1.`
1102
+ `${label} result ${index} rank was not contiguous from 1.`
1064
1103
  );
1065
1104
  }
1066
1105
  }
@@ -1080,10 +1119,7 @@ function assertPapiersSearchResultsResponse(label, body, request) {
1080
1119
  rejectUnexpectedKeys(label, value, SEARCH_RESPONSE_KEYS);
1081
1120
  assertPublicRequestId(`${label} requestId`, value.requestId);
1082
1121
  assertNonEmptyString(`${label} query`, value.query);
1083
- const itemCount = validateListSearchItems(label, value.items, {
1084
- mode: "search",
1085
- requireRank: true
1086
- });
1122
+ const itemCount = validateSearchResults(label, value.results);
1087
1123
  validatePage(label, value.page, itemCount, { cursor: "none" });
1088
1124
  validateOptionalSearchQuality(`${label} quality`, value.quality);
1089
1125
  if (value.ranking === void 0) {
@@ -1118,12 +1154,12 @@ function assertPapiersSearchResultsResponse(label, body, request) {
1118
1154
  `${label} returned more items than the consumed ranking window.`
1119
1155
  );
1120
1156
  }
1121
- const items = value.items;
1157
+ const items = value.results;
1122
1158
  let previousRank = expectedOffset;
1123
1159
  for (const [index, item] of items.entries()) {
1124
1160
  if (typeof item.rank !== "number" || item.rank <= previousRank || item.rank > consumedBound) {
1125
1161
  throw new PapiersCliResponseValidationError(
1126
- `${label} item ${index} rank was not strictly increasing within the frozen ranking window.`
1162
+ `${label} result ${index} rank was not strictly increasing within the frozen ranking window.`
1127
1163
  );
1128
1164
  }
1129
1165
  previousRank = item.rank;
@@ -1332,6 +1368,164 @@ function validateTokenSummary(label, rawToken) {
1332
1368
  validateOptionalTimestamp(`${label} revokedAt`, token.revokedAt);
1333
1369
  validateOptionalTimestamp(`${label} lastUsedAt`, token.lastUsedAt);
1334
1370
  }
1371
+ function validateSearchResults(label, results, options = {}) {
1372
+ const values = assertArray(`${label} results`, results);
1373
+ for (const [index, rawResult] of values.entries()) {
1374
+ const result = assertRecord(`${label} result ${index}`, rawResult);
1375
+ rejectUnexpectedContent(`${label} result ${index}`, result);
1376
+ const kind = validateSearchResultKind(
1377
+ `${label} result ${index} kind`,
1378
+ result.kind,
1379
+ result.id
1380
+ );
1381
+ if (options.expectedKind !== void 0 && kind !== options.expectedKind) {
1382
+ throw new PapiersCliResponseValidationError(
1383
+ `${label} result ${index} kind did not match the requested kind.`
1384
+ );
1385
+ }
1386
+ if (options.expectedKinds !== void 0 && !options.expectedKinds.has(kind)) {
1387
+ throw new PapiersCliResponseValidationError(
1388
+ `${label} result ${index} kind was not one of the requested kinds.`
1389
+ );
1390
+ }
1391
+ if (kind === "document") {
1392
+ validateSearchDocumentResult(`${label} result ${index}`, result);
1393
+ } else {
1394
+ validateSearchConversationResult(`${label} result ${index}`, result);
1395
+ }
1396
+ }
1397
+ return values.length;
1398
+ }
1399
+ function validateSearchResultKind(label, value, id) {
1400
+ if (typeof id !== "string") {
1401
+ throw new PapiersCliResponseValidationError(`${label} id was invalid.`);
1402
+ }
1403
+ if (!SEARCH_RESULT_KINDS.has(String(value))) {
1404
+ throw new PapiersCliResponseValidationError(
1405
+ `${label} was not a top-level search result kind.`
1406
+ );
1407
+ }
1408
+ if (value === "document") {
1409
+ assertDocId(`${label} id`, id);
1410
+ return "document";
1411
+ }
1412
+ if (value === "conversation" && isPublicIdForKind(id, "conversation")) {
1413
+ return "conversation";
1414
+ }
1415
+ throw new PapiersCliResponseValidationError(
1416
+ `${label} did not match the public id kind.`
1417
+ );
1418
+ }
1419
+ function validateSearchDocumentResult(label, result) {
1420
+ rejectUnexpectedKeys(label, result, SEARCH_DOCUMENT_RESULT_KEYS);
1421
+ assertDocId(`${label} id`, result.id);
1422
+ assertPositiveInteger(`${label} rank`, result.rank);
1423
+ validateSearchScore(`${label} score`, result.score);
1424
+ validateSearchMatchedBy(`${label} matchedBy`, result.matchedBy, {
1425
+ allow: /* @__PURE__ */ new Set([
1426
+ "title",
1427
+ "doi",
1428
+ "arxivId",
1429
+ "pmid",
1430
+ "passage",
1431
+ "highlight",
1432
+ "equation"
1433
+ ])
1434
+ });
1435
+ validateOptionalNullableString(`${label} title`, result.title);
1436
+ validateOptionalDocumentCatalogMetadata(`${label} metadata`, result.metadata);
1437
+ validateOptionalMetadataReadSource(`${label} source`, result.source);
1438
+ const evidenceCount = validateSearchEvidenceList(
1439
+ `${label} evidence`,
1440
+ result.evidence
1441
+ );
1442
+ const evidenceTotal = assertNonNegativeInteger(
1443
+ `${label} evidenceTotal`,
1444
+ result.evidenceTotal
1445
+ );
1446
+ if (evidenceTotal < evidenceCount) {
1447
+ throw new PapiersCliResponseValidationError(
1448
+ `${label} evidenceTotal was smaller than evidence.length.`
1449
+ );
1450
+ }
1451
+ }
1452
+ function validateSearchConversationResult(label, result) {
1453
+ rejectUnexpectedKeys(label, result, SEARCH_CONVERSATION_RESULT_KEYS);
1454
+ const id = assertNonEmptyString(`${label} id`, result.id);
1455
+ if (!isPublicIdForKind(id, "conversation")) {
1456
+ throw new PapiersCliResponseValidationError(
1457
+ `${label} id was not a cnv_* public id.`
1458
+ );
1459
+ }
1460
+ assertPositiveInteger(`${label} rank`, result.rank);
1461
+ validateSearchScore(`${label} score`, result.score);
1462
+ validateSearchMatchedBy(`${label} matchedBy`, result.matchedBy, {
1463
+ allow: /* @__PURE__ */ new Set(["conversation"])
1464
+ });
1465
+ validateOptionalNullableString(`${label} title`, result.title);
1466
+ validateOptionalPublicExcerpt(`${label} snippet`, result.snippet);
1467
+ }
1468
+ function validateSearchEvidenceList(label, value) {
1469
+ const evidence = assertArray(label, value);
1470
+ for (const [index, rawEvidence] of evidence.entries()) {
1471
+ const item = assertRecord(`${label} ${index}`, rawEvidence);
1472
+ rejectUnexpectedKeys(`${label} ${index}`, item, SEARCH_EVIDENCE_KEYS);
1473
+ validateSearchEvidenceKind(`${label} ${index} kind`, item.kind, item.id);
1474
+ assertPositiveInteger(`${label} ${index} rank`, item.rank);
1475
+ if (item.rank !== index + 1) {
1476
+ throw new PapiersCliResponseValidationError(
1477
+ `${label} ${index} rank was not contiguous from 1.`
1478
+ );
1479
+ }
1480
+ validateOptionalPublicExcerpt(`${label} ${index} snippet`, item.snippet);
1481
+ validateSearchScore(`${label} ${index} score`, item.score);
1482
+ }
1483
+ return evidence.length;
1484
+ }
1485
+ function validateSearchEvidenceKind(label, value, id) {
1486
+ if (typeof id !== "string") {
1487
+ throw new PapiersCliResponseValidationError(`${label} id was invalid.`);
1488
+ }
1489
+ if (!SEARCH_EVIDENCE_KINDS.has(String(value))) {
1490
+ throw new PapiersCliResponseValidationError(
1491
+ `${label} was not a search evidence kind.`
1492
+ );
1493
+ }
1494
+ if (value === "passage" && isPublicIdForKind(id, "passage") || value === "highlight" && isPublicIdForKind(id, "highlight") || value === "equation" && isPublicIdForKind(id, "equation")) {
1495
+ return value;
1496
+ }
1497
+ throw new PapiersCliResponseValidationError(
1498
+ `${label} did not match the public id kind.`
1499
+ );
1500
+ }
1501
+ function validateSearchMatchedBy(label, value, options) {
1502
+ const values = assertArray(label, value);
1503
+ if (values.length === 0) {
1504
+ throw new PapiersCliResponseValidationError(`${label} was empty.`);
1505
+ }
1506
+ const seen = /* @__PURE__ */ new Set();
1507
+ for (const [index, rawReason] of values.entries()) {
1508
+ const reason = assertNonEmptyString(`${label} ${index}`, rawReason);
1509
+ if (!SEARCH_MATCH_REASONS.has(reason) || !options.allow.has(reason)) {
1510
+ throw new PapiersCliResponseValidationError(
1511
+ `${label} ${index} was not an allowed search match reason.`
1512
+ );
1513
+ }
1514
+ if (seen.has(reason)) {
1515
+ throw new PapiersCliResponseValidationError(
1516
+ `${label} contained duplicate match reasons.`
1517
+ );
1518
+ }
1519
+ seen.add(reason);
1520
+ }
1521
+ }
1522
+ function validateSearchScore(label, value) {
1523
+ if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 1) {
1524
+ throw new PapiersCliResponseValidationError(
1525
+ `${label} was not a finite score in [0, 1].`
1526
+ );
1527
+ }
1528
+ }
1335
1529
  function assertNoInternalResponseKeys(label, value, options = {}) {
1336
1530
  const stack = [
1337
1531
  { value, isRoot: true }
@@ -2709,22 +2903,18 @@ function expectedKindForListSearchRequest(request) {
2709
2903
  function expectedKindsForSearchRequest(request) {
2710
2904
  const kinds = request.kinds;
2711
2905
  if (kinds === void 0) {
2712
- return {
2713
- expectedKinds: /* @__PURE__ */ new Set([
2714
- "document",
2715
- "passage",
2716
- "highlight",
2717
- "conversation"
2718
- ])
2719
- };
2906
+ return { expectedKinds: /* @__PURE__ */ new Set(["document", "conversation"]) };
2720
2907
  }
2721
- if (!Array.isArray(kinds) || kinds.length <= 1) {
2722
- return { expectedKind: expectedKindForListSearchRequest(request) };
2908
+ if (!Array.isArray(kinds) || kinds.length === 0) {
2909
+ throw new CliUsageError(
2910
+ "Public search requests must specify at least one supported kind."
2911
+ );
2723
2912
  }
2724
- for (const kind of kinds) {
2725
- if (kind === "document" || kind === "equation") {
2913
+ const rawKinds = kinds;
2914
+ for (const kind of rawKinds) {
2915
+ if (kind === "document") {
2726
2916
  throw new CliUsageError(
2727
- `Multi-kind public search does not support kind ${kind}; request it separately.`
2917
+ "Unified public search does not support kind document; omit kinds to search titles plus content."
2728
2918
  );
2729
2919
  }
2730
2920
  if (!PUBLIC_KINDS.includes(kind)) {
@@ -2733,14 +2923,16 @@ function expectedKindsForSearchRequest(request) {
2733
2923
  );
2734
2924
  }
2735
2925
  }
2736
- return {
2737
- expectedKinds: new Set(kinds)
2738
- };
2926
+ const normalizedKinds = new Set(rawKinds);
2927
+ if (normalizedKinds.size === 1 && normalizedKinds.has("conversation")) {
2928
+ return { expectedKind: "conversation" };
2929
+ }
2930
+ return normalizedKinds.has("conversation") ? { expectedKinds: /* @__PURE__ */ new Set(["document", "conversation"]) } : { expectedKind: "document" };
2739
2931
  }
2740
2932
  async function requestPapiersApi(config, fetchImpl, path, init) {
2741
2933
  if (!config.token) {
2742
2934
  throw new CliUsageError(
2743
- `${TOKEN_ENV_VAR} is required for public Papiers API commands.`
2935
+ `${TOKEN_ENV_VAR} is not set. Run papiers auth login, or set ${TOKEN_ENV_VAR}, then retry.`
2744
2936
  );
2745
2937
  }
2746
2938
  return requestPapiersApiWithBearer(
@@ -2862,7 +3054,7 @@ var PMID_PATTERN = /^\d+$/u;
2862
3054
  var YEAR_PATTERN = /^\d{4}$/u;
2863
3055
  var MIN_PUBLIC_YEAR = 1;
2864
3056
  var MAX_PUBLIC_YEAR = 9999;
2865
- var DOCUMENT_FILTER_FIELD_ALIASES = {
3057
+ var FORMER_FILTER_FIELD_ALIASES = {
2866
3058
  arxiv_id: "arxivId",
2867
3059
  last_engaged_at: "lastEngagedAt",
2868
3060
  published_date: "publishedDate",
@@ -2871,13 +3063,27 @@ var DOCUMENT_FILTER_FIELD_ALIASES = {
2871
3063
  source_family: "sourceFamily",
2872
3064
  year: "publishedYear"
2873
3065
  };
2874
- var DOCUMENT_FILTER_FIELD_ALIAS_NAMES = Object.keys(
2875
- DOCUMENT_FILTER_FIELD_ALIASES
2876
- ).sort();
3066
+ function didYouMeanFilterField(field) {
3067
+ const suggestion = FORMER_FILTER_FIELD_ALIASES[field];
3068
+ return suggestion ? ` Did you mean "${suggestion}"?` : "";
3069
+ }
2877
3070
  function parseDocumentFilterExpression(expression) {
3071
+ if (isImplicitAuthorEqualityFilterExpression(expression)) {
3072
+ throw new CliUsageError(
3073
+ 'Use "author.contains=<name-or-handle>" for author searches, or "author.eq=<full-author-key>" for exact author-key matches.'
3074
+ );
3075
+ }
2878
3076
  const filter = parseFilterExpression(expression);
2879
3077
  return normalizeSupportedDocumentFilter(filter);
2880
3078
  }
3079
+ function isImplicitAuthorEqualityFilterExpression(expression) {
3080
+ const trimmed = expression.trim();
3081
+ const equalsIndex = trimmed.indexOf("=");
3082
+ if (equalsIndex < 0) {
3083
+ return false;
3084
+ }
3085
+ return trimmed.slice(0, equalsIndex).trim() === "author";
3086
+ }
2881
3087
  function parseFilterExpression(expression) {
2882
3088
  const parsed = parseFilterParts(expression);
2883
3089
  const field = parsed.field.trim();
@@ -2903,57 +3109,52 @@ function parseFilterExpression(expression) {
2903
3109
  };
2904
3110
  }
2905
3111
  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];
3112
+ const field = filter.field;
3113
+ const allowedOperators = EXPECTED_DOCUMENT_FILTER_OPERATORS[field];
2909
3114
  if (!allowedOperators) {
2910
3115
  throw new CliUsageError(
2911
3116
  `Unsupported public document filter field "${filter.field}". Supported fields: ${Object.keys(
2912
3117
  EXPECTED_DOCUMENT_FILTER_OPERATORS
2913
- ).join(", ")}.`
3118
+ ).join(", ")}.${didYouMeanFilterField(filter.field)}`
2914
3119
  );
2915
3120
  }
2916
- if (!allowedOperators.includes(
2917
- canonicalFilter.op
2918
- )) {
3121
+ if (!allowedOperators.includes(filter.op)) {
2919
3122
  throw new CliUsageError(
2920
- `Operator "${canonicalFilter.op}" is not supported for public document filter field "${canonicalFilter.field}". Supported operators: ${allowedOperators.join(
3123
+ `Operator "${filter.op}" is not supported for public document filter field "${filter.field}". Supported operators: ${allowedOperators.join(
2921
3124
  ", "
2922
3125
  )}.`
2923
3126
  );
2924
3127
  }
2925
- switch (canonicalFilter.field) {
3128
+ const documentFilter = { ...filter, field };
3129
+ switch (field) {
2926
3130
  case "id":
2927
3131
  return normalizeStringDocumentFilter(
2928
- canonicalFilter,
3132
+ documentFilter,
2929
3133
  normalizeDocumentIdValue
2930
3134
  );
2931
3135
  case "doi":
2932
- return normalizeStringDocumentFilter(canonicalFilter, normalizeDoiOrNull);
3136
+ return normalizeStringDocumentFilter(documentFilter, normalizeDoiOrNull);
2933
3137
  case "arxivId":
2934
3138
  return normalizeStringDocumentFilter(
2935
- canonicalFilter,
3139
+ documentFilter,
2936
3140
  normalizeArxivOrNull
2937
3141
  );
2938
3142
  case "pmid":
2939
- return normalizeStringDocumentFilter(
2940
- canonicalFilter,
2941
- normalizePmidOrNull
2942
- );
3143
+ return normalizeStringDocumentFilter(documentFilter, normalizePmidOrNull);
2943
3144
  case "publishedDate":
2944
- return normalizePublishedDateFilter(canonicalFilter);
3145
+ return normalizePublishedDateFilter(documentFilter);
2945
3146
  case "publishedYear":
2946
- return normalizePublishedYearFilter(canonicalFilter);
3147
+ return normalizePublishedYearFilter(documentFilter);
2947
3148
  case "sourceDomain":
2948
- return normalizeSourceDomainFilter(canonicalFilter);
3149
+ return normalizeSourceDomainFilter(documentFilter);
2949
3150
  case "annotated":
2950
3151
  case "engaged":
2951
- return normalizeBooleanDocumentFilter(canonicalFilter);
3152
+ return normalizeBooleanDocumentFilter(documentFilter);
2952
3153
  case "lastEngagedAt":
2953
- return normalizeLastEngagedAtFilter(canonicalFilter);
3154
+ return normalizeLastEngagedAtFilter(documentFilter);
2954
3155
  default:
2955
3156
  return normalizeStringDocumentFilter(
2956
- canonicalFilter,
3157
+ documentFilter,
2957
3158
  normalizeNonEmptyString
2958
3159
  );
2959
3160
  }
@@ -3001,12 +3202,6 @@ function normalizeBooleanDocumentFilter(filter) {
3001
3202
  }
3002
3203
  throw invalidFilterValue(filter);
3003
3204
  }
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
3205
  function normalizePublishedDateFilter(filter) {
3011
3206
  const normalized = normalizePapiersPublicDateFilter(
3012
3207
  filter.op,
@@ -3293,21 +3488,22 @@ function parseScalar(rawValue) {
3293
3488
 
3294
3489
  // src/args.ts
3295
3490
  var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3491
+ "all",
3296
3492
  "force",
3297
3493
  "help",
3298
3494
  "doc-ids",
3495
+ "evidence-ids",
3496
+ "highlight-ids",
3299
3497
  "ids",
3300
3498
  "ids0",
3301
3499
  "input0",
3302
3500
  "json",
3501
+ "passage-ids",
3303
3502
  "pretty",
3304
- "stdin",
3305
- "token-stdin",
3306
3503
  "fail-fast",
3307
3504
  "version"
3308
3505
  ]);
3309
3506
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
3310
- "api-base-url",
3311
3507
  "api-url",
3312
3508
  "cursor",
3313
3509
  "document-id",
@@ -3326,8 +3522,13 @@ var VALUE_FLAGS = /* @__PURE__ */ new Set([
3326
3522
  "since"
3327
3523
  ]);
3328
3524
  var SECRET_ARG_FLAGS = /* @__PURE__ */ new Set(["pat", "token", "api-token"]);
3329
- var GLOBAL_VALUE_FLAGS = /* @__PURE__ */ new Set(["api-base-url", "api-url"]);
3525
+ var GLOBAL_VALUE_FLAGS = /* @__PURE__ */ new Set(["api-url"]);
3330
3526
  var NO_COMMAND_VALUE_FLAGS = /* @__PURE__ */ new Set([...GLOBAL_VALUE_FLAGS]);
3527
+ var REMOVED_FLAG_HINTS = {
3528
+ "api-base-url": "Use --api-url instead.",
3529
+ "token-stdin": "papiers auth login reads the token from stdin \u2014 no flag needed.",
3530
+ stdin: "papiers auth login reads the token from stdin \u2014 no flag needed."
3531
+ };
3331
3532
  var NO_COMMAND_BOOLEAN_FLAGS = /* @__PURE__ */ new Set(["help", "json"]);
3332
3533
  var IDS_PIPELINE_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3333
3534
  "help",
@@ -3338,6 +3539,12 @@ var IDS_PIPELINE_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3338
3539
  "json",
3339
3540
  "pretty"
3340
3541
  ]);
3542
+ var SEARCH_IDS_PIPELINE_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3543
+ ...IDS_PIPELINE_BOOLEAN_FLAGS,
3544
+ "evidence-ids",
3545
+ "highlight-ids",
3546
+ "passage-ids"
3547
+ ]);
3341
3548
  var LIST_VALUE_FLAGS = /* @__PURE__ */ new Set([
3342
3549
  ...GLOBAL_VALUE_FLAGS,
3343
3550
  "cursor",
@@ -3378,122 +3585,309 @@ var READ_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3378
3585
  ]);
3379
3586
  var AUTH_LOGIN_BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3380
3587
  ...NO_COMMAND_BOOLEAN_FLAGS,
3381
- "force",
3382
- "stdin",
3383
- "token-stdin"
3588
+ "force"
3384
3589
  ]);
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"],
3590
+ var FORMER_KIND_ALIASES = /* @__PURE__ */ new Map([
3389
3591
  ["doc", "document"],
3390
3592
  ["docs", "document"],
3391
- ["passage", "passage"],
3593
+ ["documents", "document"],
3392
3594
  ["passages", "passage"],
3393
- ["highlight", "highlight"],
3394
- ["highlights", "highlight"],
3395
3595
  ["annotation", "highlight"],
3396
3596
  ["annotations", "highlight"],
3397
- ["conversation", "conversation"],
3398
- ["conversations", "conversation"],
3597
+ ["highlights", "highlight"],
3399
3598
  ["chat", "conversation"],
3400
3599
  ["chats", "conversation"],
3401
- ["equation", "equation"],
3600
+ ["conversations", "conversation"],
3402
3601
  ["equations", "equation"]
3403
3602
  ]);
3404
3603
  var MULTI_SEARCH_KINDS = [
3405
3604
  "passage",
3406
3605
  "highlight",
3407
- "conversation"
3606
+ "conversation",
3607
+ "equation"
3408
3608
  ];
3409
- var HELP_TEXT = `Papiers CLI
3609
+ var GLOBAL_HELP = `Papiers CLI: search and read your library from the terminal.
3610
+ Your saved papers, highlights, and conversations (your chats) \u2014 for you or an AI agent.
3410
3611
 
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.
3612
+ GETTING STARTED
3613
+ papiers auth login Sign in with a token from Papiers (Settings -> API tokens)
3614
+ papiers list --limit 5 --pretty Your most recent documents
3615
+ papiers search "your topic" --pretty Search across your library
3413
3616
 
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]
3617
+ COMMANDS
3618
+ search Search across your library
3619
+ list List your documents, highlights, and conversations
3620
+ read Print a document, highlight, or conversation
3621
+ browse Page through an earlier search's results
3622
+ auth Sign in, check status, or sign out
3623
+ tokens List or revoke your API tokens
3624
+ doctor Check your setup, connection, and what's available
3625
+ version Print the CLI version
3626
+
3627
+ papiers <command> --help full options and examples for a command
3628
+ papiers help --all everything, in one page
3629
+
3630
+ OUTPUT
3631
+ Commands print JSON by default \u2014 built for scripts and agents.
3632
+ --pretty human-readable output
3633
+ --ids / --doc-ids pipe result IDs into the next command
3634
+
3635
+ SIGNING IN
3636
+ Signing in saves your token on this machine.
3637
+ For scripts or an AI agent, set PAPIERS_TOKEN instead.
3638
+
3639
+ Exit codes: 0 ok \xB7 1 command or API failure \xB7 2 bad usage`;
3640
+ var OUTPUT_OPTS = ` --json machine-readable JSON (the default)
3641
+ --pretty human-readable text
3642
+ --ids result/item IDs, one per line, with their native prefix
3643
+ --ids0 the same IDs, NUL-separated, for \`xargs -0\`
3644
+ --doc-ids parent document IDs only (doc_\u2026), deduped; conversation hits are skipped`;
3645
+ var SEARCH_OUTPUT_OPTS = `${OUTPUT_OPTS}
3646
+ --passage-ids matching passage evidence IDs, flattened in result order
3647
+ --highlight-ids matching highlight evidence IDs, flattened in result order
3648
+ --evidence-ids all matching evidence IDs, flattened in result order`;
3649
+ var HELP_TOPICS = {
3650
+ search: `papiers search \u2014 search across your library.
3651
+
3652
+ USAGE
3653
+ papiers search QUERY [--kind KIND ...] [--filter FIELD.OP=VALUE ...] [--limit N] [--pretty|--json|--ids|--ids0|--doc-ids|--passage-ids|--highlight-ids|--evidence-ids]
3654
+
3655
+ OPTIONS
3656
+ --kind KIND what to search. Defaults to all \u2014 your passages, highlights,
3657
+ equations, conversations, and matching titles. Or focus on one:
3658
+ passage \xB7 highlight \xB7 conversation \xB7 equation.
3659
+ --filter FIELD.OP=VALUE narrow parent documents \u2014 repeatable, up to 25.
3660
+ e.g. --filter publishedYear.gte=2020 --filter author.contains=hinton. Fields/ops: help --all.
3661
+ --document-id ID scope to documents: doc_\u2026 (comma-separated or repeated), or @-/@file.
3662
+ --limit N max results, 1\u201350.
3663
+ ${SEARCH_OUTPUT_OPTS}
3664
+
3665
+ EXAMPLES
3666
+ papiers search "mixture of experts" --pretty
3667
+ papiers search "capacity factors" --kind passage --document-id doc_<id> --json
3668
+ papiers search "dropout" --filter author.contains=hinton --json
3669
+ papiers search "diffusion" --filter venue.contains=neurips --json
3670
+ papiers search retrieval --kind passage --limit 5 --passage-ids | papiers read @- --format plain --json
3671
+
3672
+ When capabilities.searchResultsBrowse.enabled is true, a search may return ranking.searchId \u2014 pass it to \`papiers browse\` for more pages.`,
3673
+ list: `papiers list \u2014 list your documents, highlights, and conversations.
3674
+
3675
+ USAGE
3676
+ papiers list [--kind KIND] [--limit N] [--cursor CURSOR] [--pretty|--json|--ids|--ids0|--doc-ids]
3677
+ papiers list --kind document [--filter FIELD.OP=VALUE ...] [--order-by KEY] [--document-id doc_\u2026|@-|@file]
3678
+ papiers list --kind highlight|conversation [--since WHEN]
3679
+
3680
+ OPTIONS
3681
+ --kind KIND document (default) \xB7 passage \xB7 highlight \xB7 conversation \xB7 equation.
3682
+ --filter FIELD.OP=VALUE narrow document results \u2014 repeatable, up to 25. document kind only.
3683
+ e.g. --filter lastEngagedAt.gte=30d --filter annotated=true. Fields/ops: help --all.
3684
+ --order-by KEY addedAt:desc (default) or lastEngagedAt:desc. document kind only.
3685
+ --document-id ID scope to documents: doc_\u2026 (comma-separated or repeated), or @-/@file.
3686
+ document and highlight only.
3687
+ --since WHEN only items at or after an ISO 8601 time, or an Nd shorthand (e.g. 30d).
3688
+ highlight and conversation only.
3689
+ --limit N max results, 1\u201350.
3690
+ --cursor CURSOR fetch the next page \u2014 use page.nextCursor from the previous response.
3691
+ ${OUTPUT_OPTS}
3692
+
3693
+ EXAMPLES
3694
+ papiers list --limit 5 --pretty
3695
+ papiers list --kind highlight --since 30d --pretty
3696
+ papiers list --kind document --filter lastEngagedAt.gte=30d --json
3697
+ papiers list --kind document --filter annotated=true --json`,
3698
+ read: `papiers read \u2014 print documents, highlights, conversations, passages, or equations.
3699
+
3700
+ USAGE
3701
+ papiers read ID [ID ...] [--format FORMAT] [--max-chars-per-item N] [--fail-fast] [--json|--pretty]
3702
+ papiers read @- | @file [--input0] [--format FORMAT] [--max-chars-per-item N] [--json|--pretty]
3703
+ Each ID is doc_\u2026 / pas_\u2026 / hl_\u2026 / cnv_\u2026 / eq_\u2026 \u2014 the kind is read from the prefix; don't mix kinds.
3704
+
3705
+ OPTIONS
3706
+ --format FORMAT how to render each item. Default auto (equation defaults to metadata).
3707
+ document: auto|metadata|markdown|plain. passage: markdown|plain.
3708
+ highlight, conversation: metadata|plain. equation: metadata.
3709
+ --include SECTION extra sections, comma-separated: metadata, source, locations.
3710
+ document only (highlight/conversation/equation: metadata only; passage: none).
3711
+ --pages RANGE restrict a document read to pages, e.g. 1,3-5. document only.
3712
+ --max-chars-per-item N truncate each item's content to N chars, 1\u2013200000.
3713
+ --cursor CURSOR continue one long document \u2014 use items[].nextCursor from the previous
3714
+ response. One literal doc_\u2026 only, with --format auto, markdown, or plain.
3715
+ --fail-fast exit non-zero if any item fails (default: succeed if at least one does).
3716
+ --json machine-readable JSON (the default)
3717
+ --pretty human-readable text
3718
+
3719
+ EXAMPLES
3720
+ papiers read doc_<id> --format markdown --max-chars-per-item 2000 --json
3721
+ papiers read hl_<id> --json
3722
+ papiers search retrieval --doc-ids | papiers read @- --format metadata --json`,
3723
+ browse: `papiers browse \u2014 page deeper into an earlier search's results.
3724
+
3725
+ USAGE
3726
+ papiers browse srch_\u2026 [--offset N] [--limit N] [--pretty|--json|--ids|--ids0|--doc-ids|--passage-ids|--highlight-ids|--evidence-ids]
3727
+
3728
+ OPTIONS
3729
+ --offset N start at rank N (0-based) in the frozen ranking.
3730
+ --limit N max results, 1\u201350.
3731
+ ${SEARCH_OUTPUT_OPTS}
3732
+
3733
+ When capabilities.searchResultsBrowse.enabled is true, a search response includes
3734
+ ranking.searchId (an srch_\u2026 handle); pass it here to page deeper without
3735
+ re-running the search. The ranking is frozen and expires after a while.
3736
+
3737
+ EXAMPLE
3738
+ papiers browse srch_<id> --offset 10 --json`,
3739
+ auth: `papiers auth \u2014 sign in, check status, or sign out.
3740
+
3741
+ USAGE
3742
+ papiers auth login [--force] [--json] Sign in (paste your token, or pipe it in)
3743
+ papiers auth status [--json] Show whether you're signed in
3744
+ papiers auth inspect [--json] Show details about the saved token
3745
+ papiers auth logout [--json] Forget the saved token
3746
+
3747
+ OPTIONS
3748
+ --force when signing in, replace an already-saved token.
3749
+
3750
+ Signing in saves your token on this machine. For scripts or an AI agent, set PAPIERS_TOKEN instead.`,
3751
+ tokens: `papiers tokens \u2014 list or revoke your API tokens.
3752
+
3753
+ USAGE
3421
3754
  papiers tokens list [--json]
3422
3755
  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
3756
 
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
3757
+ Create and name tokens in Papiers (Settings -> API tokens). Revoking is permanent.`,
3758
+ doctor: `papiers doctor \u2014 check your setup, connection, and what's available.
3460
3759
 
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.
3760
+ USAGE
3761
+ papiers doctor [--json]
3469
3762
 
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.
3763
+ Verifies your token, the server connection, and which features are available.
3764
+ Run it first when something isn't working.`,
3765
+ version: `papiers version \u2014 print the CLI version.
3473
3766
 
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.
3767
+ USAGE
3768
+ papiers version | papiers --version | papiers -V`
3769
+ };
3770
+ var REFERENCE = `REFERENCE \u2014 IDs, piping, pagination, and limits.
3477
3771
 
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
- `;
3772
+ ID PREFIXES
3773
+ doc_ document pas_ passage hl_ highlight
3774
+ cnv_ conversation eq_ equation srch_ a saved search ranking (for browse)
3775
+ On read, the kind \u2014 and which --format/--include apply \u2014 is read from the prefix.
3776
+
3777
+ PIPING IDS BETWEEN COMMANDS
3778
+ --ids result IDs, one per line, with their native prefix
3779
+ --ids0 the same IDs, NUL-separated (for \`xargs -0\`)
3780
+ --doc-ids parent document IDs only (doc_\u2026), deduped; conversation hits are skipped
3781
+ @- read IDs from stdin @file read IDs from a file
3782
+ --input0 read piped IDs NUL-separated instead of whitespace/newlines
3783
+ @- and @file accept plain IDs or a Papiers JSON envelope, and may be mixed with literal IDs.
3784
+
3785
+ PAGINATION \u2014 three separate mechanisms
3786
+ list response has page.nextCursor \u2192 papiers list \u2026 --cursor CURSOR
3787
+ search response has ranking.searchId \u2192 papiers browse srch_\u2026 --offset N
3788
+ read a truncated item has items[].nextCursor \u2192 papiers read doc_\u2026 --cursor CURSOR
3789
+
3790
+ LIMITS
3791
+ --limit 1\u201350 \xB7 --max-chars-per-item 1\u2013200000 \xB7 up to 25 filters \xB7 up to 100 IDs per request
3792
+
3793
+ FILTER FIELDS \u2014 document kind, as FIELD.OP=VALUE (or FIELD=VALUE for equality)
3794
+ id title author venue doi arxivId pmid
3795
+ publishedDate publishedYear sourceFamily sourceDomain
3796
+ annotated engaged lastEngagedAt
3797
+ Operators: eq \xB7 in \xB7 contains \xB7 prefix \xB7 gte \xB7 gt \xB7 lte \xB7 lt \xB7 between (which apply depends on the field).`;
3798
+ var HELP_PAGES = {
3799
+ ...HELP_TOPICS,
3800
+ reference: REFERENCE
3801
+ };
3802
+ var HELP_TOPIC_NAMES = Object.keys(HELP_PAGES);
3803
+ var HELP_COMMAND_NAMES = Object.keys(HELP_TOPICS);
3804
+ var HELP_TEXT = [GLOBAL_HELP, ...Object.values(HELP_PAGES)].join(
3805
+ "\n\n\n"
3806
+ );
3807
+ function colorizeHelp(text) {
3808
+ return text.replace(/^([A-Z][A-Z ]{2,})$/gm, "\x1B[1;36m$1\x1B[0m").replace(
3809
+ /(^|[\s([|/])(--[a-z0-9-]+|-[A-Za-z])\b/g,
3810
+ "$1\x1B[33m$2\x1B[0m"
3811
+ );
3812
+ }
3813
+ function renderHelp(command, style) {
3814
+ const help = command.type === "help" ? command : void 0;
3815
+ let body = GLOBAL_HELP;
3816
+ if (help?.all) {
3817
+ body = HELP_TEXT;
3818
+ } else if (help?.topic) {
3819
+ body = HELP_PAGES[help.topic] ?? GLOBAL_HELP;
3820
+ }
3821
+ return style ? colorizeHelp(body) : body;
3822
+ }
3823
+ var SUGGESTIBLE_COMMANDS = [
3824
+ "auth",
3825
+ "doctor",
3826
+ "capabilities",
3827
+ "tokens",
3828
+ "list",
3829
+ "search",
3830
+ "browse",
3831
+ "read",
3832
+ "version",
3833
+ "help"
3834
+ ];
3835
+ function levenshtein(a, b) {
3836
+ const rows = a.length + 1;
3837
+ const cols = b.length + 1;
3838
+ const dist = Array.from(
3839
+ { length: rows },
3840
+ () => new Array(cols).fill(0)
3841
+ );
3842
+ for (let i = 0; i < rows; i += 1) dist[i][0] = i;
3843
+ for (let j = 0; j < cols; j += 1) dist[0][j] = j;
3844
+ for (let i = 1; i < rows; i += 1) {
3845
+ for (let j = 1; j < cols; j += 1) {
3846
+ const cost = a[i - 1] === b[j - 1] ? 0 : 1;
3847
+ dist[i][j] = Math.min(
3848
+ dist[i - 1][j] + 1,
3849
+ dist[i][j - 1] + 1,
3850
+ dist[i - 1][j - 1] + cost
3851
+ );
3852
+ }
3853
+ }
3854
+ return dist[rows - 1][cols - 1];
3855
+ }
3856
+ function suggestCommand(input) {
3857
+ let best = null;
3858
+ let bestDistance = Number.POSITIVE_INFINITY;
3859
+ for (const candidate of SUGGESTIBLE_COMMANDS) {
3860
+ const distance = levenshtein(input, candidate);
3861
+ if (distance < bestDistance) {
3862
+ bestDistance = distance;
3863
+ best = candidate;
3864
+ }
3865
+ }
3866
+ return best !== null && bestDistance <= 2 ? best : null;
3867
+ }
3868
+ function unknownCommandMessage(command) {
3869
+ const suggestion = suggestCommand(command);
3870
+ const guess = suggestion ? ` Did you mean "${suggestion}"?` : "";
3871
+ return `Unknown papiers command.${guess} Run papiers --help to see commands.`;
3872
+ }
3483
3873
  function parseCliArgs(argv) {
3484
3874
  const parsed = parseRawArgv(argv);
3485
3875
  const global = parseGlobalOptions(parsed);
3486
3876
  if (parsed.booleanFlags.has("version") && !parsed.help) {
3487
3877
  return { type: "version", ...global };
3488
3878
  }
3489
- if (parsed.help || parsed.positionals.length === 0) {
3879
+ const [command, ...rest] = parsed.positionals;
3880
+ if (parsed.help || command === "help" || parsed.positionals.length === 0) {
3881
+ if (parsed.booleanFlags.has("all")) {
3882
+ return { type: "help", all: true, ...global };
3883
+ }
3884
+ const topic = command === "help" ? rest[0] : command;
3885
+ if (topic && HELP_TOPIC_NAMES.includes(topic)) {
3886
+ return { type: "help", topic, ...global };
3887
+ }
3490
3888
  return { type: "help", ...global };
3491
3889
  }
3492
- const [command, ...rest] = parsed.positionals;
3493
3890
  switch (command) {
3494
- case "help":
3495
- case "--help":
3496
- return { type: "help", ...global };
3497
3891
  case "version":
3498
3892
  assertNoExtraPositionals(command, rest);
3499
3893
  return { type: "version", ...global };
@@ -3527,7 +3921,7 @@ function parseCliArgs(argv) {
3527
3921
  type: "list",
3528
3922
  ...global,
3529
3923
  idsOnly: listOutput.idsOnly,
3530
- ...parsePrettyOutput(parsed, listOutput.idsOnly) ? { pretty: true } : {},
3924
+ ...parsePrettyOutput(parsed, listOutput.anyIdsOutput) ? { pretty: true } : {},
3531
3925
  ...listOutput.idsNulDelimited ? { idsNulDelimited: true } : {},
3532
3926
  ...listOutput.docIdsOnly ? { docIdsOnly: true } : {},
3533
3927
  ...parsed.booleanFlags.has("input0") ? { inputNulDelimited: true } : {},
@@ -3537,16 +3931,23 @@ function parseCliArgs(argv) {
3537
3931
  }
3538
3932
  case "search": {
3539
3933
  assertAllowedFlags(parsed, SEARCH_VALUE_FLAGS, "search");
3540
- assertAllowedBooleanFlags(parsed, IDS_PIPELINE_BOOLEAN_FLAGS, "search");
3934
+ assertAllowedBooleanFlags(
3935
+ parsed,
3936
+ SEARCH_IDS_PIPELINE_BOOLEAN_FLAGS,
3937
+ "search"
3938
+ );
3541
3939
  const searchDocumentIds = parseDocumentIdFlagValues(parsed, "search");
3542
3940
  const searchOutput = parseIdsOutput(parsed, "search");
3543
3941
  return {
3544
3942
  type: "search",
3545
3943
  ...global,
3546
3944
  idsOnly: searchOutput.idsOnly,
3547
- ...parsePrettyOutput(parsed, searchOutput.idsOnly) ? { pretty: true } : {},
3945
+ ...parsePrettyOutput(parsed, searchOutput.anyIdsOutput) ? { pretty: true } : {},
3548
3946
  ...searchOutput.idsNulDelimited ? { idsNulDelimited: true } : {},
3549
3947
  ...searchOutput.docIdsOnly ? { docIdsOnly: true } : {},
3948
+ ...searchOutput.passageIdsOnly ? { passageIdsOnly: true } : {},
3949
+ ...searchOutput.highlightIdsOnly ? { highlightIdsOnly: true } : {},
3950
+ ...searchOutput.evidenceIdsOnly ? { evidenceIdsOnly: true } : {},
3550
3951
  ...parsed.booleanFlags.has("input0") ? { inputNulDelimited: true } : {},
3551
3952
  ...searchDocumentIds ? { documentIds: searchDocumentIds } : {},
3552
3953
  request: buildSearchRequest(rest, parsed, searchDocumentIds)
@@ -3554,7 +3955,11 @@ function parseCliArgs(argv) {
3554
3955
  }
3555
3956
  case "browse": {
3556
3957
  assertAllowedFlags(parsed, BROWSE_VALUE_FLAGS, "browse");
3557
- assertAllowedBooleanFlags(parsed, IDS_PIPELINE_BOOLEAN_FLAGS, "browse");
3958
+ assertAllowedBooleanFlags(
3959
+ parsed,
3960
+ SEARCH_IDS_PIPELINE_BOOLEAN_FLAGS,
3961
+ "browse"
3962
+ );
3558
3963
  if (parsed.booleanFlags.has("input0")) {
3559
3964
  throw new CliUsageError("--input0 is not supported for browse.");
3560
3965
  }
@@ -3563,9 +3968,12 @@ function parseCliArgs(argv) {
3563
3968
  type: "browse",
3564
3969
  ...global,
3565
3970
  idsOnly: browseOutput.idsOnly,
3566
- ...parsePrettyOutput(parsed, browseOutput.idsOnly) ? { pretty: true } : {},
3971
+ ...parsePrettyOutput(parsed, browseOutput.anyIdsOutput) ? { pretty: true } : {},
3567
3972
  ...browseOutput.idsNulDelimited ? { idsNulDelimited: true } : {},
3568
3973
  ...browseOutput.docIdsOnly ? { docIdsOnly: true } : {},
3974
+ ...browseOutput.passageIdsOnly ? { passageIdsOnly: true } : {},
3975
+ ...browseOutput.highlightIdsOnly ? { highlightIdsOnly: true } : {},
3976
+ ...browseOutput.evidenceIdsOnly ? { evidenceIdsOnly: true } : {},
3569
3977
  request: buildBrowseRequest(rest, parsed)
3570
3978
  };
3571
3979
  }
@@ -3584,7 +3992,7 @@ function parseCliArgs(argv) {
3584
3992
  };
3585
3993
  }
3586
3994
  default:
3587
- throw new CliUsageError("Unknown papiers command.");
3995
+ throw new CliUsageError(unknownCommandMessage(command));
3588
3996
  }
3589
3997
  }
3590
3998
  function argvWantsJson(argv) {
@@ -3594,9 +4002,9 @@ function parsePrettyOutput(parsed, idsOnly) {
3594
4002
  if (!parsed.booleanFlags.has("pretty")) {
3595
4003
  return false;
3596
4004
  }
3597
- if (idsOnly || parsed.json || parsed.booleanFlags.has("doc-ids")) {
4005
+ if (idsOnly || parsed.json) {
3598
4006
  throw new CliUsageError(
3599
- "--pretty cannot be combined with --json, --ids, --ids0, or --doc-ids."
4007
+ "--pretty cannot be combined with --json or ID projection flags."
3600
4008
  );
3601
4009
  }
3602
4010
  return true;
@@ -3636,12 +4044,6 @@ function parseAuthCommand(rest, parsed, global) {
3636
4044
  case "login": {
3637
4045
  assertAllowedBooleanFlags(parsed, AUTH_LOGIN_BOOLEAN_FLAGS, "auth login");
3638
4046
  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
4047
  return {
3646
4048
  type: "auth-login",
3647
4049
  ...global,
@@ -3792,104 +4194,65 @@ function buildSearchRequest(positionals, parsed, documentIds) {
3792
4194
  "Ranked public search cursors are not available yet."
3793
4195
  );
3794
4196
  }
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")) {
4197
+ if (parsed.flags.has("query-in")) {
3843
4198
  throw new CliUsageError(
3844
- "papiers search --since is supported only with --kind highlight or conversation."
4199
+ "papiers search no longer supports --query-in; use free-text query plus --filter FIELD.OP=VALUE for structured attributes."
3845
4200
  );
3846
4201
  }
3847
- if (queryIn === "latex") {
3848
- throw new CliUsageError(
3849
- "papiers search --query-in latex requires --kind equation."
3850
- );
3851
- }
3852
- if (queryIn !== void 0 && queryIn !== "content") {
4202
+ if (parsed.flags.has("since")) {
3853
4203
  throw new CliUsageError(
3854
- "papiers search supports --query-in title, metadata, content, or latex."
4204
+ "papiers search no longer supports --since; use --filter lastEngagedAt.gte=<when> for document recency."
3855
4205
  );
3856
4206
  }
4207
+ const kinds = parseSearchKindScopes(parsed);
4208
+ const filters = parsed.flags.get("filter")?.map((filter) => parseDocumentFilterExpression(filter));
4209
+ assertFilterCount(filters, "search");
3857
4210
  return pruneUndefined({
3858
4211
  schemaVersion: PAPIERS_SCHEMA_VERSIONS.searchRequest,
3859
4212
  query,
3860
- queryIn,
4213
+ kinds,
4214
+ filters,
3861
4215
  limit: parseLimit(parsed)
3862
4216
  });
3863
4217
  }
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
- );
4218
+ function parseSearchKindScopes(parsed) {
4219
+ const rawKinds = parsed.flags.get("kind")?.flatMap(splitCommaSeparatedValues);
4220
+ if (rawKinds === void 0) {
4221
+ return void 0;
3869
4222
  }
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
- );
4223
+ if (rawKinds.length === 0) {
4224
+ throw new CliUsageError("papiers search --kind requires a value.");
3874
4225
  }
3875
- if (parsed.flags.has("filter")) {
3876
- throw new CliUsageError(
3877
- "papiers search --filter is not supported with --kind all."
3878
- );
4226
+ const sawAll = rawKinds.some((kind) => kind.trim().toLowerCase() === "all");
4227
+ if (sawAll) {
4228
+ if (rawKinds.length > 1) {
4229
+ throw new CliUsageError(
4230
+ "papiers search --kind all cannot be combined with other --kind values."
4231
+ );
4232
+ }
4233
+ return void 0;
3879
4234
  }
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
- );
4235
+ const kinds = [];
4236
+ for (const rawKind of rawKinds) {
4237
+ const kind = normalizeCliPublicKind(rawKind);
4238
+ if (!kind) {
4239
+ throw new CliUsageError(
4240
+ `papiers search supports --kind passage, highlight, conversation, or equation; received "${rawKind}".${didYouMeanKind(rawKind)}`
4241
+ );
4242
+ }
4243
+ if (kind === "document") {
4244
+ throw new CliUsageError(
4245
+ "papiers search no longer supports --kind document; omit --kind to search matching titles and content."
4246
+ );
4247
+ }
4248
+ if (!MULTI_SEARCH_KINDS.includes(kind)) {
4249
+ throw new CliUsageError(
4250
+ `papiers search supports --kind passage, highlight, conversation, or equation; received "${rawKind}".`
4251
+ );
4252
+ }
4253
+ kinds.push(kind);
3885
4254
  }
3886
- return pruneUndefined({
3887
- schemaVersion: PAPIERS_SCHEMA_VERSIONS.searchRequest,
3888
- query,
3889
- kinds: [...MULTI_SEARCH_KINDS],
3890
- queryIn: "content",
3891
- limit: parseLimit(parsed)
3892
- });
4255
+ return [...new Set(kinds)];
3893
4256
  }
3894
4257
  function parseSinceFlag(parsed, kind, command) {
3895
4258
  const value = singleFlagValue(parsed, "since");
@@ -3964,7 +4327,7 @@ function parseReadKind(parsed) {
3964
4327
  return normalized;
3965
4328
  }
3966
4329
  throw new CliUsageError(
3967
- `Public CLI read currently supports --kind document, passage, highlight, conversation, or equation only; received "${kind}". ${PUBLIC_KIND_ALIAS_HELP}`
4330
+ `Public CLI read currently supports --kind document, passage, highlight, conversation, or equation only; received "${kind}".${didYouMeanKind(kind)}`
3968
4331
  );
3969
4332
  }
3970
4333
  function readKindsForLiteralIds(ids) {
@@ -4023,13 +4386,25 @@ function parseIdsOutput(parsed, command) {
4023
4386
  const idsOnly = parsed.booleanFlags.has("ids");
4024
4387
  const idsNulDelimited = parsed.booleanFlags.has("ids0");
4025
4388
  const docIdsOnly = parsed.booleanFlags.has("doc-ids");
4026
- const selectedCount = Number(idsOnly) + Number(idsNulDelimited) + Number(docIdsOnly) + Number(parsed.json);
4389
+ const passageIdsOnly = parsed.booleanFlags.has("passage-ids");
4390
+ const highlightIdsOnly = parsed.booleanFlags.has("highlight-ids");
4391
+ const evidenceIdsOnly = parsed.booleanFlags.has("evidence-ids");
4392
+ const selectedCount = Number(idsOnly) + Number(idsNulDelimited) + Number(docIdsOnly) + Number(passageIdsOnly) + Number(highlightIdsOnly) + Number(evidenceIdsOnly) + Number(parsed.json);
4027
4393
  if (selectedCount > 1) {
4028
4394
  throw new CliUsageError(
4029
- `Use only one of --ids, --ids0, --doc-ids, or --json for papiers ${command}.`
4395
+ `Use only one of --ids, --ids0, --doc-ids, --passage-ids, --highlight-ids, --evidence-ids, or --json for papiers ${command}.`
4030
4396
  );
4031
4397
  }
4032
- return { idsOnly: idsOnly || idsNulDelimited, idsNulDelimited, docIdsOnly };
4398
+ const normalizedIdsOnly = idsOnly || idsNulDelimited;
4399
+ return {
4400
+ idsOnly: normalizedIdsOnly,
4401
+ idsNulDelimited,
4402
+ docIdsOnly,
4403
+ passageIdsOnly,
4404
+ highlightIdsOnly,
4405
+ evidenceIdsOnly,
4406
+ anyIdsOutput: normalizedIdsOnly || docIdsOnly || passageIdsOnly || highlightIdsOnly || evidenceIdsOnly
4407
+ };
4033
4408
  }
4034
4409
  function parseReadInclude(parsed) {
4035
4410
  const rawIncludes = parsed.flags.get("include")?.flatMap(splitCommaSeparatedValues);
@@ -4190,7 +4565,8 @@ function parseRawArgv(argv) {
4190
4565
  continue;
4191
4566
  }
4192
4567
  if (!VALUE_FLAGS.has(name)) {
4193
- throw new CliUsageError(`Unknown option --${name}.`);
4568
+ const hint = REMOVED_FLAG_HINTS[name] ?? "Run papiers --help to see options.";
4569
+ throw new CliUsageError(`Unknown option --${name}. ${hint}`);
4194
4570
  }
4195
4571
  const flagValue = value ?? argv[index + 1];
4196
4572
  if (flagValue === void 0 || flagValue.startsWith("--")) {
@@ -4221,14 +4597,9 @@ function splitLongFlag(arg) {
4221
4597
  };
4222
4598
  }
4223
4599
  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
4600
  return {
4230
4601
  json: parsed.json,
4231
- apiBaseUrl: apiBaseUrl ?? apiUrlAlias
4602
+ apiBaseUrl: singleFlagValue(parsed, "api-url")
4232
4603
  };
4233
4604
  }
4234
4605
  function parseListSearchKind(parsed, command) {
@@ -4243,11 +4614,16 @@ function parseListSearchKind(parsed, command) {
4243
4614
  );
4244
4615
  }
4245
4616
  throw new CliUsageError(
4246
- `papiers ${command} supports --kind document, passage, highlight, conversation, or equation; received "${kind}". ${PUBLIC_KIND_ALIAS_HELP}`
4617
+ `papiers ${command} supports --kind document, passage, highlight, conversation, or equation; received "${kind}".${didYouMeanKind(kind)}`
4247
4618
  );
4248
4619
  }
4249
4620
  function normalizeCliPublicKind(value) {
4250
- return PUBLIC_KIND_ALIASES.get(value.trim().toLowerCase()) ?? null;
4621
+ const normalized = value.trim().toLowerCase();
4622
+ return PUBLIC_KINDS.includes(normalized) ? normalized : null;
4623
+ }
4624
+ function didYouMeanKind(value) {
4625
+ const suggestion = FORMER_KIND_ALIASES.get(value.trim().toLowerCase());
4626
+ return suggestion ? ` Did you mean "${suggestion}"?` : "";
4251
4627
  }
4252
4628
  function assertNoNonDocumentListSearchFlag(parsed, flag, command, kind) {
4253
4629
  if (parsed.flags.has(flag)) {
@@ -4300,34 +4676,6 @@ function parseLimit(parsed) {
4300
4676
  }
4301
4677
  return limit;
4302
4678
  }
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
4679
  function assertFilterCount(filters, command, reservedFilterCount = 0) {
4332
4680
  const totalFilterCount = (filters?.length ?? 0) + reservedFilterCount;
4333
4681
  if (totalFilterCount > DOCUMENT_MAX_FILTERS) {
@@ -4511,26 +4859,7 @@ function assertSupportedDocumentCapabilities(value) {
4511
4859
  assertSupportedSearchResultsBrowse(capabilities.searchResultsBrowse);
4512
4860
  const queryIn = assertRecord2("capabilities queryIn", capabilities.queryIn);
4513
4861
  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
- );
4862
+ assertPublicKindStringArrayRecord("capabilities queryIn", queryIn, {});
4534
4863
  const readFormats = assertStringArray(
4535
4864
  "capabilities readFormats",
4536
4865
  capabilities.readFormats
@@ -4619,7 +4948,11 @@ function assertSupportedDocumentCapabilities(value) {
4619
4948
  `capabilities filters.${kind}`,
4620
4949
  filters[kind]
4621
4950
  );
4622
- assertSameStringSet(`capabilities filters.${kind}`, actualFilters, []);
4951
+ assertSameStringSet(
4952
+ `capabilities filters.${kind}`,
4953
+ actualFilters,
4954
+ kind === "conversation" ? EXPECTED_CONVERSATION_FILTERS : []
4955
+ );
4623
4956
  }
4624
4957
  const operatorsByField = assertRecord2(
4625
4958
  "capabilities operatorsByField",
@@ -4723,12 +5056,9 @@ function assertPublicKindStringArrayRecord(label, record, expectedByKind) {
4723
5056
  }
4724
5057
  }
4725
5058
  function assertSupportedSearchKinds(searchKinds) {
4726
- if (!searchKinds.includes("document")) {
4727
- throw new Error("capabilities searchKinds did not include document.");
4728
- }
4729
5059
  const seen = /* @__PURE__ */ new Set();
4730
5060
  for (const kind of searchKinds) {
4731
- if (seen.has(kind) || !PUBLIC_KINDS.includes(kind)) {
5061
+ if (seen.has(kind) || kind === "document" || !PUBLIC_KINDS.includes(kind)) {
4732
5062
  throw new Error(
4733
5063
  `capabilities searchKinds advertised unsupported kind: ${kind}.`
4734
5064
  );
@@ -4950,9 +5280,16 @@ function isPublicNotReadyReason(reason, reasons) {
4950
5280
  }
4951
5281
 
4952
5282
  // src/pretty.ts
5283
+ function warningLines(warnings) {
5284
+ if (!warnings?.length) {
5285
+ return [];
5286
+ }
5287
+ return warnings.map((warning) => warning.message?.trim()).filter((message) => Boolean(message)).map((message) => `! ${message}`);
5288
+ }
4953
5289
  function renderPrettyPageOutput(response) {
4954
5290
  const lines = [];
4955
- for (const item of response.items) {
5291
+ const entries = response.results ?? response.items ?? [];
5292
+ for (const item of entries) {
4956
5293
  const rank = item.rank !== void 0 ? `${item.rank}. ` : "";
4957
5294
  const title = item.title?.trim() ? ` ${item.title.trim()}` : "";
4958
5295
  lines.push(`${rank}${item.id} [${item.kind}]${title}`);
@@ -4960,21 +5297,36 @@ function renderPrettyPageOutput(response) {
4960
5297
  if (excerpt?.trim()) {
4961
5298
  lines.push(` ${excerpt.trim()}`);
4962
5299
  }
5300
+ if (item.evidence?.length) {
5301
+ for (const evidence of item.evidence) {
5302
+ const evidenceRank = evidence.rank !== void 0 ? `${evidence.rank}. ` : "";
5303
+ lines.push(` - ${evidenceRank}${evidence.id} [${evidence.kind}]`);
5304
+ if (evidence.snippet?.trim()) {
5305
+ lines.push(` ${evidence.snippet.trim()}`);
5306
+ }
5307
+ }
5308
+ if (typeof item.evidenceTotal === "number" && item.evidenceTotal > item.evidence.length) {
5309
+ lines.push(
5310
+ ` + ${item.evidenceTotal - item.evidence.length} more evidence item${item.evidenceTotal - item.evidence.length === 1 ? "" : "s"}`
5311
+ );
5312
+ }
5313
+ }
4963
5314
  }
4964
5315
  if (lines.length === 0) {
4965
5316
  lines.push("(no items)");
4966
5317
  }
4967
- const itemCount = response.items.length;
5318
+ const itemCount = entries.length;
4968
5319
  lines.push(
4969
5320
  `(${itemCount} item${itemCount === 1 ? "" : "s"}; ${pagingSummary(response)})`
4970
5321
  );
5322
+ lines.push(...warningLines(response.warnings));
4971
5323
  return `${lines.join("\n")}
4972
5324
  `;
4973
5325
  }
4974
5326
  function pagingSummary(response) {
4975
5327
  const ranking = response.ranking;
4976
5328
  if (ranking) {
4977
- const nextOffset = ranking.nextOffset !== void 0 ? ranking.nextOffset : ranking.totalCount > response.items.length ? response.items.length : null;
5329
+ const nextOffset = ranking.nextOffset !== void 0 ? ranking.nextOffset : ranking.totalCount > (response.results ?? response.items ?? []).length ? (response.results ?? response.items ?? []).length : null;
4978
5330
  return nextOffset === null ? `ranking exhausted at ${ranking.totalCount}` : `${ranking.totalCount} ranked in total \u2014 papiers browse ${ranking.searchId} --offset ${nextOffset}`;
4979
5331
  }
4980
5332
  return response.page.nextCursor ? "more available \u2014 rerun with --json and pass page.nextCursor via --cursor" : "no more pages";
@@ -5054,7 +5406,9 @@ async function runCli(argv, dependencies = {}) {
5054
5406
  const command = parseCliArgs(argv);
5055
5407
  jsonOutput = command.json;
5056
5408
  if (command.type === "help") {
5057
- stdout.write(HELP_TEXT);
5409
+ const styled = dependencies.stdout === void 0 && process.stdout.isTTY === true && process.env.NO_COLOR === void 0;
5410
+ stdout.write(`${renderHelp(command, styled)}
5411
+ `);
5058
5412
  return 0;
5059
5413
  }
5060
5414
  if (command.type === "version") {
@@ -5126,12 +5480,27 @@ async function runCli(argv, dependencies = {}) {
5126
5480
  json: command.json,
5127
5481
  stdout
5128
5482
  });
5129
- case "capabilities":
5130
- writeJson(
5131
- stdout,
5132
- await getCapabilities(config, resolveFetch(dependencies.fetch))
5483
+ case "capabilities": {
5484
+ const capabilities = await getCapabilities(
5485
+ config,
5486
+ resolveFetch(dependencies.fetch)
5487
+ );
5488
+ if (command.json) {
5489
+ writeJson(stdout, capabilities);
5490
+ return 0;
5491
+ }
5492
+ const caps = capabilities;
5493
+ stdout.write(
5494
+ `${redactSensitiveText(
5495
+ [
5496
+ `Validation: ok requestId=${String(caps.requestId ?? "")}`,
5497
+ ...formatCapabilityLines(caps)
5498
+ ].join("\n")
5499
+ )}
5500
+ `
5133
5501
  );
5134
5502
  return 0;
5503
+ }
5135
5504
  case "tokens-list":
5136
5505
  writeTokenListOutput(
5137
5506
  stdout,
@@ -5198,11 +5567,17 @@ async function runCli(argv, dependencies = {}) {
5198
5567
  request
5199
5568
  );
5200
5569
  if (command.idsOnly) {
5201
- writeIds(stdout, response.items, {
5570
+ writeIds(stdout, response.results, {
5202
5571
  nulDelimited: command.idsNulDelimited === true
5203
5572
  });
5204
5573
  } else if (command.docIdsOnly) {
5205
- writeParentDocumentIds(stdout, response.items);
5574
+ writeParentDocumentIds(stdout, response.results);
5575
+ } else if (command.passageIdsOnly) {
5576
+ writeEvidenceIds(stdout, response.results, "passage");
5577
+ } else if (command.highlightIdsOnly) {
5578
+ writeEvidenceIds(stdout, response.results, "highlight");
5579
+ } else if (command.evidenceIdsOnly) {
5580
+ writeEvidenceIds(stdout, response.results);
5206
5581
  } else if (command.pretty) {
5207
5582
  stdout.write(redactSensitiveText(renderPrettyPageOutput(response)));
5208
5583
  } else {
@@ -5218,11 +5593,17 @@ async function runCli(argv, dependencies = {}) {
5218
5593
  command.request
5219
5594
  );
5220
5595
  if (command.idsOnly) {
5221
- writeIds(stdout, response.items, {
5596
+ writeIds(stdout, response.results, {
5222
5597
  nulDelimited: command.idsNulDelimited === true
5223
5598
  });
5224
5599
  } else if (command.docIdsOnly) {
5225
- writeParentDocumentIds(stdout, response.items);
5600
+ writeParentDocumentIds(stdout, response.results);
5601
+ } else if (command.passageIdsOnly) {
5602
+ writeEvidenceIds(stdout, response.results, "passage");
5603
+ } else if (command.highlightIdsOnly) {
5604
+ writeEvidenceIds(stdout, response.results, "highlight");
5605
+ } else if (command.evidenceIdsOnly) {
5606
+ writeEvidenceIds(stdout, response.results);
5226
5607
  } else if (command.pretty) {
5227
5608
  stdout.write(redactSensitiveText(renderPrettyPageOutput(response)));
5228
5609
  } else {
@@ -5351,6 +5732,7 @@ function writeAuthLoginOutput(stdout, output, json) {
5351
5732
  return;
5352
5733
  }
5353
5734
  stdout.write("Signed in to Papiers.\n");
5735
+ stdout.write("Next, try: papiers list --limit 5 --pretty\n");
5354
5736
  }
5355
5737
  function writeAuthLogoutOutput(stdout, output, json) {
5356
5738
  if (json) {
@@ -5369,6 +5751,14 @@ function writeAuthLogoutOutput(stdout, output, json) {
5369
5751
  stdout.write(`${redactSensitiveText(lines.join("\n"))}
5370
5752
  `);
5371
5753
  }
5754
+ function formatCapabilityLines(capabilities) {
5755
+ return [
5756
+ `Scopes: ${formatUnknownList(capabilities.scopes)}`,
5757
+ `Search kinds: ${formatUnknownList(capabilities.searchKinds)}`,
5758
+ `Read formats: ${formatUnknownList(capabilities.readFormats)}`,
5759
+ `Not ready: ${formatNotReady(capabilities.notReady)}`
5760
+ ];
5761
+ }
5372
5762
  function writeAuthInspectOutput(stdout, output, json) {
5373
5763
  if (json) {
5374
5764
  writeJson(stdout, output);
@@ -5384,10 +5774,7 @@ function writeAuthInspectOutput(stdout, output, json) {
5384
5774
  `Account: userId=${String(
5385
5775
  capabilities.account?.userId ?? "unknown"
5386
5776
  )} 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)}`
5777
+ ...formatCapabilityLines(capabilities)
5391
5778
  ].join("\n")
5392
5779
  )}
5393
5780
  `
@@ -5543,13 +5930,13 @@ function normalizeStdinToken(rawInput) {
5543
5930
  const parts = rawInput.trim().split(/\s+/u).filter(Boolean);
5544
5931
  if (parts.length !== 1) {
5545
5932
  throw new CliUsageError(
5546
- "Token stdin must contain exactly one Papiers API token."
5933
+ "Token stdin must contain exactly one Papiers API token (starting with pap_live_), with no surrounding whitespace."
5547
5934
  );
5548
5935
  }
5549
5936
  const token = parts[0];
5550
5937
  if (!isPapiersPat(token)) {
5551
5938
  throw new CliUsageError(
5552
- "Token stdin did not contain a valid Papiers API token."
5939
+ "Token stdin did not contain a valid Papiers API token; expected one starting with pap_live_."
5553
5940
  );
5554
5941
  }
5555
5942
  return token;
@@ -5715,6 +6102,27 @@ function writeParentDocumentIds(stdout, items) {
5715
6102
  stdout.write(ids.length === 0 ? "" : `${ids.join("\n")}
5716
6103
  `);
5717
6104
  }
6105
+ function writeEvidenceIds(stdout, results, kind) {
6106
+ const ids = [];
6107
+ for (const result of results) {
6108
+ if (result.kind !== "document") {
6109
+ continue;
6110
+ }
6111
+ for (const evidence of result.evidence) {
6112
+ if (kind !== void 0 && evidence.kind !== kind) {
6113
+ continue;
6114
+ }
6115
+ if (publicReadKindForId(evidence.id) !== evidence.kind) {
6116
+ throw new Error(
6117
+ "Papiers API returned an evidence id that did not match its kind in evidence ID output."
6118
+ );
6119
+ }
6120
+ ids.push(evidence.id);
6121
+ }
6122
+ }
6123
+ stdout.write(ids.length === 0 ? "" : `${ids.join("\n")}
6124
+ `);
6125
+ }
5718
6126
  async function expandListDocumentIdRequest(request, documentIds, input) {
5719
6127
  const ids = await expandDocumentIdPlaceholders(documentIds, "list", input);
5720
6128
  if (!ids?.length) {
@@ -6036,7 +6444,7 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6036
6444
  }
6037
6445
  if (input.startsWith("[")) {
6038
6446
  throw new CliUsageError(
6039
- `${context} JSON ${sourceLabel} must be a Papiers envelope with schemaVersion and items[].id.`
6447
+ `${context} JSON ${sourceLabel} must be a Papiers envelope with schemaVersion and items[].id or results[].id.`
6040
6448
  );
6041
6449
  }
6042
6450
  let parsed;
@@ -6049,7 +6457,7 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6049
6457
  }
6050
6458
  if (!parsed || typeof parsed !== "object") {
6051
6459
  throw new CliUsageError(
6052
- `${context} JSON ${sourceLabel} must be a Papiers envelope with schemaVersion and items[].id.`
6460
+ `${context} JSON ${sourceLabel} must be a Papiers envelope with schemaVersion and items[].id or results[].id.`
6053
6461
  );
6054
6462
  }
6055
6463
  const envelope = parsed;
@@ -6062,7 +6470,7 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6062
6470
  if (envelope.schemaVersion === PAPIERS_SCHEMA_VERSIONS.list) {
6063
6471
  assertPapiersListResponse(`${context} list envelope`, parsed);
6064
6472
  } else if (envelope.schemaVersion === PAPIERS_SCHEMA_VERSIONS.search) {
6065
- assertPapiersSearchResponse(`${context} search envelope`, parsed);
6473
+ validateSearchPipelineEnvelope(`${context} search envelope`, parsed);
6066
6474
  } else {
6067
6475
  assertPapiersReadPipelineEnvelope(`${context} read envelope`, parsed);
6068
6476
  }
@@ -6074,10 +6482,10 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6074
6482
  }
6075
6483
  throw error;
6076
6484
  }
6077
- const items = envelope.items;
6485
+ const items = envelope.schemaVersion === PAPIERS_SCHEMA_VERSIONS.search ? envelope.results : envelope.items;
6078
6486
  if (!Array.isArray(items)) {
6079
6487
  throw new CliUsageError(
6080
- `${context} JSON ${sourceLabel} must be a Papiers envelope with items[].id.`
6488
+ `${context} JSON ${sourceLabel} must be a Papiers envelope with items[].id or results[].id.`
6081
6489
  );
6082
6490
  }
6083
6491
  return items.map((item, index) => {
@@ -6102,6 +6510,19 @@ function parseJsonEnvelopeIds(input, context, sourceLabel) {
6102
6510
  return id;
6103
6511
  });
6104
6512
  }
6513
+ function validateSearchPipelineEnvelope(label, parsed) {
6514
+ const envelope = parsed;
6515
+ const ranking = envelope.ranking && typeof envelope.ranking === "object" && !Array.isArray(envelope.ranking) ? envelope.ranking : null;
6516
+ if (ranking && ("offset" in ranking || "nextOffset" in ranking)) {
6517
+ assertPapiersSearchResultsResponse(label, parsed, {
6518
+ schemaVersion: PAPIERS_SCHEMA_VERSIONS.searchResultsRequest,
6519
+ searchId: typeof ranking.searchId === "string" ? ranking.searchId : "",
6520
+ offset: typeof ranking.offset === "number" ? ranking.offset : void 0
6521
+ });
6522
+ return;
6523
+ }
6524
+ assertPapiersSearchResponse(label, parsed);
6525
+ }
6105
6526
  function parseTextIds(input) {
6106
6527
  return input.split(/[\s,]+/u).filter(Boolean);
6107
6528
  }
@@ -6120,9 +6541,20 @@ function writeCliError(error, params) {
6120
6541
  `${redactSensitiveText(serialized.body.error.message)}
6121
6542
  `
6122
6543
  );
6544
+ const hint = humanErrorHint(error);
6545
+ if (hint) {
6546
+ params.stderr.write(`${hint}
6547
+ `);
6548
+ }
6123
6549
  }
6124
6550
  return serialized.exitCode;
6125
6551
  }
6552
+ function humanErrorHint(error) {
6553
+ if (error instanceof PapiersCliApiError && error.status === 401) {
6554
+ return "Your token may be expired or revoked. Run papiers auth login --force to replace it.";
6555
+ }
6556
+ return null;
6557
+ }
6126
6558
  function serializeCliError(error) {
6127
6559
  if (error instanceof PapiersCliApiError) {
6128
6560
  return {