papiers 0.1.5 → 0.1.8

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
@@ -9,6 +9,7 @@ import { realpathSync } from "fs";
9
9
  import { fileURLToPath as fileURLToPath2 } from "url";
10
10
 
11
11
  // src/commands.ts
12
+ import { randomUUID as randomUUID2 } from "crypto";
12
13
  import { readFile, stat } from "fs/promises";
13
14
  import { isAbsolute, resolve as resolve2 } from "path";
14
15
 
@@ -412,6 +413,177 @@ function isNodeError(error) {
412
413
  return Boolean(error && typeof error === "object" && "code" in error);
413
414
  }
414
415
 
416
+ // src/contracts.ts
417
+ var PAPIERS_SCHEMA_VERSIONS = {
418
+ searchRequest: "papiers.search.request.v1",
419
+ search: "papiers.search.v1",
420
+ searchResultsRequest: "papiers.search.results.request.v1",
421
+ listRequest: "papiers.list.request.v1",
422
+ list: "papiers.list.v1",
423
+ readRequest: "papiers.read.request.v1",
424
+ read: "papiers.read.v1",
425
+ capabilities: "papiers.capabilities.v1",
426
+ importRequest: "papiers.import.request.v1",
427
+ import: "papiers.import.v1",
428
+ highlightRequest: "papiers.highlight.request.v1",
429
+ highlight: "papiers.highlight.v1",
430
+ feedLinkRequest: "papiers.discover.feed.link.request.v1",
431
+ feedLink: "papiers.discover.feed.link.v1",
432
+ error: "papiers.error.v1"
433
+ };
434
+ var PAPIERS_PUBLIC_DATA_PLANE_SCOPES = [
435
+ "library:metadata:read",
436
+ "library:search",
437
+ "content:search",
438
+ "content:snippet:read",
439
+ "content:read",
440
+ "content:structure:read"
441
+ ];
442
+ var PAPIERS_PUBLIC_FEATURE_IDS = [
443
+ "document_content_read",
444
+ "non_document_kinds",
445
+ "content_search",
446
+ "list_cursors",
447
+ "read_cursors",
448
+ "read_section_scopes",
449
+ "read_context",
450
+ "read_locations",
451
+ "ranked_search_cursors",
452
+ "search_results_browse",
453
+ "public_id_projection_not_ready",
454
+ "queryIn:content",
455
+ "queryIn:all",
456
+ "queryIn:latex"
457
+ ];
458
+ var PAPIERS_SEARCH_QUALITY_SIGNALS = [
459
+ "high",
460
+ "medium",
461
+ "low",
462
+ "empty"
463
+ ];
464
+ var PAPIERS_SEARCH_RESULT_KINDS = [
465
+ "document",
466
+ "conversation",
467
+ "note"
468
+ ];
469
+ var PAPIERS_SEARCH_EVIDENCE_KINDS = [
470
+ "passage",
471
+ "highlight",
472
+ "equation"
473
+ ];
474
+ var PAPIERS_SEARCH_MATCH_REASONS = [
475
+ "title",
476
+ "doi",
477
+ "arxivId",
478
+ "pmid",
479
+ "passage",
480
+ "highlight",
481
+ "equation",
482
+ "conversation",
483
+ "note"
484
+ ];
485
+ var PAPIERS_PUBLIC_NOT_READY_REASONS_BY_FEATURE = {
486
+ document_content_read: ["explicit_enablement_required"],
487
+ non_document_kinds: ["public_id_projection_not_ready"],
488
+ content_search: ["content_search_not_ready"],
489
+ list_cursors: ["cursor_signing_not_configured"],
490
+ read_cursors: [
491
+ "cursor_signing_not_configured",
492
+ "document_content_read_required"
493
+ ],
494
+ read_section_scopes: ["section_scoped_read_not_ready"],
495
+ read_context: ["read_context_not_ready"],
496
+ ranked_search_cursors: ["search_sessions_not_ready"],
497
+ search_results_browse: [
498
+ "content_search_not_ready",
499
+ "search_sessions_not_ready"
500
+ ]
501
+ };
502
+ var PAPIERS_PUBLIC_ERROR_CODES = [
503
+ "api_disabled",
504
+ "auth_missing",
505
+ "auth_invalid_format",
506
+ "auth_invalid",
507
+ "auth_wrong_surface",
508
+ "auth_scope_missing",
509
+ "feature_not_available",
510
+ "filter_not_supported",
511
+ "invalid_filter_field",
512
+ "invalid_filter_operator",
513
+ "invalid_filter_value",
514
+ "invalid_filter_combination",
515
+ "order_not_supported",
516
+ "unsupported_format",
517
+ "invalid_cursor",
518
+ "invalid_cursor_context",
519
+ "forbidden",
520
+ "invalid_request",
521
+ "not_found",
522
+ "source_superseded",
523
+ "stale_reference",
524
+ "content_limit_exceeded",
525
+ "request_limit_exceeded",
526
+ "rate_limited",
527
+ "internal_error"
528
+ ];
529
+
530
+ // ../../src/shared/papiers-public-ids.ts
531
+ var PAPIERS_PUBLIC_ID_PREFIX_BY_KIND = {
532
+ document: "doc",
533
+ highlight: "hl",
534
+ conversation: "cnv",
535
+ section: "sec",
536
+ figure: "fig",
537
+ table: "tbl",
538
+ note: "nte"
539
+ };
540
+ var PAPIERS_PUBLIC_ID_KIND_BY_PREFIX = Object.fromEntries(
541
+ Object.entries(PAPIERS_PUBLIC_ID_PREFIX_BY_KIND).map(([kind, prefix]) => [
542
+ prefix,
543
+ kind
544
+ ])
545
+ );
546
+ var PAPIERS_PUBLIC_ID_PATTERN = /^(doc|hl|cnv|sec|fig|tbl|nte)_([a-f0-9]{32})$/;
547
+ var PAPIERS_PUBLIC_REF_PATTERN = /^(doc_[a-f0-9]{32})#(?:p(0|[1-9][0-9]{0,9})|e([a-z0-9_]{1,64}))\.([a-f0-9]{8})$/;
548
+ var MAX_CHUNK_INDEX = 2147483647;
549
+ function parsePapiersPublicId(value) {
550
+ const id = value.trim();
551
+ const ref = id.match(PAPIERS_PUBLIC_REF_PATTERN);
552
+ if (ref) {
553
+ const [, documentId, chunkIndex, astNodeId, fingerprint] = ref;
554
+ if (chunkIndex !== void 0) {
555
+ const index = Number(chunkIndex);
556
+ if (index > MAX_CHUNK_INDEX) return null;
557
+ return {
558
+ id,
559
+ kind: "passage",
560
+ documentId,
561
+ chunkIndex: index,
562
+ fingerprint
563
+ };
564
+ }
565
+ return { id, kind: "equation", documentId, astNodeId, fingerprint };
566
+ }
567
+ const match = id.match(PAPIERS_PUBLIC_ID_PATTERN);
568
+ if (!match) {
569
+ return null;
570
+ }
571
+ const prefix = match[1];
572
+ const kind = PAPIERS_PUBLIC_ID_KIND_BY_PREFIX[prefix];
573
+ if (!kind) {
574
+ return null;
575
+ }
576
+ return {
577
+ id,
578
+ kind,
579
+ prefix,
580
+ opaqueId: match[2]
581
+ };
582
+ }
583
+ function staleReferenceMessage(kind) {
584
+ return kind === "passage" ? "Passage text has changed. Search or read the document again." : "Equation text has changed. Search or read the document again.";
585
+ }
586
+
415
587
  // ../../src/shared/papiers-resource-locators.ts
416
588
  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
589
  var PAPIERS_RESOURCE_LOCATOR_PATTERN_SOURCE = `papiers://(?:note|highlight|item|conversation|artifact)/${UUID_PATTERN}`;
@@ -533,54 +705,20 @@ var DOCUMENT_MAX_FILTER_VALUES = DOCUMENT_MAX_IDS;
533
705
  var DOCUMENT_DEFAULT_READ_CHARS_PER_ITEM = 5e4;
534
706
  var DOCUMENT_MAX_READ_CHARS_PER_ITEM = 2e5;
535
707
  var DOCUMENT_MAX_READ_CHARS_PER_REQUEST = 5e5;
536
- var DOCUMENT_PUBLIC_ID_PATTERN = /^doc_[a-f0-9]{32}$/u;
537
- var PASSAGE_PUBLIC_ID_PATTERN = /^pas_[a-f0-9]{32}$/u;
538
- var HIGHLIGHT_PUBLIC_ID_PATTERN = /^hl_[a-f0-9]{32}$/u;
539
- var CONVERSATION_PUBLIC_ID_PATTERN = /^cnv_[a-f0-9]{32}$/u;
540
- var EQUATION_PUBLIC_ID_PATTERN = /^eq_[a-f0-9]{32}$/u;
541
- var NOTE_PUBLIC_ID_PATTERN = /^nte_[a-f0-9]{32}$/u;
542
708
  var SEARCH_RANKING_ID_PATTERN = /^srch_[a-f0-9]{32}$/u;
543
709
  function isSearchRankingId(value) {
544
710
  return SEARCH_RANKING_ID_PATTERN.test(value);
545
711
  }
546
- function isDocumentPublicId(value) {
547
- return DOCUMENT_PUBLIC_ID_PATTERN.test(value);
548
- }
549
- function isPassagePublicId(value) {
550
- return PASSAGE_PUBLIC_ID_PATTERN.test(value);
551
- }
552
- function isHighlightPublicId(value) {
553
- return HIGHLIGHT_PUBLIC_ID_PATTERN.test(value);
554
- }
555
- function isConversationPublicId(value) {
556
- return CONVERSATION_PUBLIC_ID_PATTERN.test(value);
557
- }
558
- function isEquationPublicId(value) {
559
- return EQUATION_PUBLIC_ID_PATTERN.test(value);
560
- }
561
- function isNotePublicId(value) {
562
- return NOTE_PUBLIC_ID_PATTERN.test(value);
563
- }
712
+ var CLI_READABLE_KINDS = new Set(PUBLIC_KINDS);
564
713
  function publicReadKindForId(value) {
565
- if (isDocumentPublicId(value)) {
566
- return "document";
567
- }
568
- if (isPassagePublicId(value)) {
569
- return "passage";
570
- }
571
- if (isHighlightPublicId(value)) {
572
- return "highlight";
573
- }
574
- if (isConversationPublicId(value)) {
575
- return "conversation";
576
- }
577
- if (isEquationPublicId(value)) {
578
- return "equation";
579
- }
580
- if (isNotePublicId(value)) {
581
- return "note";
714
+ const parsed = parsePapiersPublicId(value);
715
+ if (!parsed || parsed.id !== value || !CLI_READABLE_KINDS.has(parsed.kind)) {
716
+ return null;
582
717
  }
583
- return null;
718
+ return parsed.kind;
719
+ }
720
+ function isDocumentPublicId(value) {
721
+ return publicReadKindForId(value) === "document";
584
722
  }
585
723
  function readKindForIdentifier(value) {
586
724
  const publicKind = publicReadKindForId(value);
@@ -597,118 +735,6 @@ function isPublicIdForKind(value, kind) {
597
735
  return publicReadKindForId(value) === kind;
598
736
  }
599
737
 
600
- // src/contracts.ts
601
- var PAPIERS_SCHEMA_VERSIONS = {
602
- searchRequest: "papiers.search.request.v1",
603
- search: "papiers.search.v1",
604
- searchResultsRequest: "papiers.search.results.request.v1",
605
- listRequest: "papiers.list.request.v1",
606
- list: "papiers.list.v1",
607
- readRequest: "papiers.read.request.v1",
608
- read: "papiers.read.v1",
609
- capabilities: "papiers.capabilities.v1",
610
- tokenList: "papiers.tokens.list.response.v1",
611
- tokenRevoke: "papiers.tokens.revoke.response.v1",
612
- error: "papiers.error.v1"
613
- };
614
- var PAPIERS_PUBLIC_DATA_PLANE_SCOPES = [
615
- "library:metadata:read",
616
- "library:search",
617
- "content:search",
618
- "content:snippet:read",
619
- "content:read",
620
- "content:structure:read"
621
- ];
622
- var PAPIERS_PUBLIC_FEATURE_IDS = [
623
- "document_content_read",
624
- "non_document_kinds",
625
- "content_search",
626
- "list_cursors",
627
- "read_cursors",
628
- "read_section_scopes",
629
- "read_context",
630
- "read_locations",
631
- "ranked_search_cursors",
632
- "search_results_browse",
633
- "public_id_projection_not_ready",
634
- "queryIn:content",
635
- "queryIn:all",
636
- "queryIn:latex"
637
- ];
638
- var PAPIERS_SEARCH_QUALITY_SIGNALS = [
639
- "high",
640
- "medium",
641
- "low",
642
- "empty"
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
- ];
665
- var PAPIERS_PUBLIC_NOT_READY_REASONS_BY_FEATURE = {
666
- document_content_read: ["explicit_enablement_required"],
667
- non_document_kinds: ["public_id_projection_not_ready"],
668
- content_search: [
669
- "content_search_not_ready",
670
- "public_passage_projection_not_ready"
671
- ],
672
- list_cursors: ["cursor_signing_not_configured"],
673
- read_cursors: [
674
- "cursor_signing_not_configured",
675
- "document_content_read_required"
676
- ],
677
- read_section_scopes: ["section_scoped_read_not_ready"],
678
- read_context: ["read_context_not_ready"],
679
- ranked_search_cursors: ["search_sessions_not_ready"],
680
- search_results_browse: [
681
- "content_search_not_ready",
682
- "search_sessions_not_ready"
683
- ]
684
- };
685
- var PAPIERS_PUBLIC_ERROR_CODES = [
686
- "api_disabled",
687
- "auth_missing",
688
- "auth_invalid_format",
689
- "auth_invalid",
690
- "auth_wrong_surface",
691
- "auth_scope_missing",
692
- "feature_not_available",
693
- "filter_not_supported",
694
- "invalid_filter_field",
695
- "invalid_filter_operator",
696
- "invalid_filter_value",
697
- "invalid_filter_combination",
698
- "order_not_supported",
699
- "unsupported_format",
700
- "invalid_cursor",
701
- "invalid_cursor_context",
702
- "forbidden",
703
- "invalid_request",
704
- "not_found",
705
- "source_superseded",
706
- "content_limit_exceeded",
707
- "request_limit_exceeded",
708
- "rate_limited",
709
- "internal_error"
710
- ];
711
-
712
738
  // ../../src/shared/papiers-public-source-domain.ts
713
739
  var papiers_public_source_domain_exports = {};
714
740
  __export(papiers_public_source_domain_exports, {
@@ -799,9 +825,6 @@ function isTokenUuid(value) {
799
825
  }
800
826
 
801
827
  // src/response-validation.ts
802
- var PUBLIC_DATA_PLANE_SCOPE_SET = new Set(
803
- PAPIERS_PUBLIC_DATA_PLANE_SCOPES
804
- );
805
828
  var PUBLIC_ERROR_CODE_SET = new Set(PAPIERS_PUBLIC_ERROR_CODES);
806
829
  var SAFE_REQUEST_ID_PATTERN = /^[A-Za-z0-9._:-]{1,128}$/u;
807
830
  var LIST_RESPONSE_KEYS = /* @__PURE__ */ new Set([
@@ -850,26 +873,6 @@ var READ_RESPONSE_KEYS = /* @__PURE__ */ new Set([
850
873
  "items",
851
874
  "usage"
852
875
  ]);
853
- var TOKEN_LIST_RESPONSE_KEYS = /* @__PURE__ */ new Set([
854
- "schemaVersion",
855
- "requestId",
856
- "tokens"
857
- ]);
858
- var TOKEN_REVOKE_RESPONSE_KEYS = /* @__PURE__ */ new Set([
859
- "schemaVersion",
860
- "requestId",
861
- "tokenId",
862
- "revoked"
863
- ]);
864
- var TOKEN_SUMMARY_KEYS = /* @__PURE__ */ new Set([
865
- "tokenId",
866
- "name",
867
- "scopes",
868
- "createdAt",
869
- "expiresAt",
870
- "revokedAt",
871
- "lastUsedAt"
872
- ]);
873
876
  var PAGE_KEYS = /* @__PURE__ */ new Set(["nextCursor", "returnedCount"]);
874
877
  var READ_USAGE_KEYS = /* @__PURE__ */ new Set(["returnedChars", "truncatedItemCount"]);
875
878
  var LIST_DOCUMENT_ITEM_KEYS = /* @__PURE__ */ new Set([
@@ -949,6 +952,7 @@ var READ_SUCCESS_PASSAGE_ITEM_KEYS = /* @__PURE__ */ new Set([
949
952
  "truncated",
950
953
  "charCount",
951
954
  "tokenEstimate",
955
+ "metadata",
952
956
  "warnings"
953
957
  ]);
954
958
  var READ_SUCCESS_HIGHLIGHT_ITEM_KEYS = /* @__PURE__ */ new Set([
@@ -1056,6 +1060,7 @@ var CONVERSATION_READ_NOT_FOUND_MESSAGE = "Conversation is not available.";
1056
1060
  var EQUATION_READ_NOT_FOUND_MESSAGE = "Equation is not available.";
1057
1061
  var NOTE_READ_NOT_FOUND_MESSAGE = "Note is not available.";
1058
1062
  var DOCUMENT_READ_SOURCE_SUPERSEDED_CODE = "source_superseded";
1063
+ var READ_STALE_REFERENCE_CODE = "stale_reference";
1059
1064
  var CONVERSATION_READ_CONTENT_LIMIT_CODE = "content_limit_exceeded";
1060
1065
  var CONVERSATION_READ_REQUEST_LIMIT_CODE = "request_limit_exceeded";
1061
1066
  var DOCUMENT_READ_SOURCE_SUPERSEDED_MESSAGE = "Document content is not available for the current source.";
@@ -1299,28 +1304,64 @@ function assertPapiersReadResponse(label, body, expectedIds, expected = "metadat
1299
1304
  function assertPapiersReadPipelineEnvelope(label, body) {
1300
1305
  validatePapiersReadResponse(label, body);
1301
1306
  }
1302
- function assertPapiersTokenListResponse(label, body) {
1303
- assertNoInternalResponseKeys(label, body);
1304
- const value = assertSchema(label, body, PAPIERS_SCHEMA_VERSIONS.tokenList);
1305
- rejectUnexpectedKeys(label, value, TOKEN_LIST_RESPONSE_KEYS);
1307
+ var IMPORT_RESPONSE_KEYS = /* @__PURE__ */ new Set([
1308
+ "schemaVersion",
1309
+ "requestId",
1310
+ "operationId",
1311
+ "status",
1312
+ "documentId",
1313
+ "error"
1314
+ ]);
1315
+ var IMPORT_ERROR_KEYS = new Set(
1316
+ Object.keys({
1317
+ code: true,
1318
+ message: true,
1319
+ retryable: true,
1320
+ details: true
1321
+ })
1322
+ );
1323
+ var IMPORT_ERROR_DETAIL_KEYS = /* @__PURE__ */ new Set(["importCode"]);
1324
+ function assertPapiersImportResponse(label, body) {
1325
+ const value = assertSchema(label, body, PAPIERS_SCHEMA_VERSIONS.import);
1326
+ rejectUnexpectedKeys(label, value, IMPORT_RESPONSE_KEYS);
1306
1327
  assertPublicRequestId(`${label} requestId`, value.requestId);
1307
- const tokens = assertArray(`${label} tokens`, value.tokens);
1308
- for (const [index, rawToken] of tokens.entries()) {
1309
- validateTokenSummary(`${label} token ${index}`, rawToken);
1328
+ assertNonEmptyString(`${label} operationId`, value.operationId);
1329
+ assertNonEmptyString(`${label} status`, value.status);
1330
+ if (value.documentId !== null) {
1331
+ assertDocumentReadIdentifier(`${label} documentId`, value.documentId);
1332
+ }
1333
+ if (value.error !== null) {
1334
+ const error = assertRecord(`${label} error`, value.error);
1335
+ rejectUnexpectedKeys(`${label} error`, error, IMPORT_ERROR_KEYS);
1336
+ assertPublicErrorCode(`${label} error.code`, error.code);
1337
+ assertNonEmptyString(`${label} error.message`, error.message);
1338
+ assertBoolean(`${label} error.retryable`, error.retryable);
1339
+ const details = assertRecord(`${label} error.details`, error.details);
1340
+ rejectUnexpectedKeys(
1341
+ `${label} error.details`,
1342
+ details,
1343
+ IMPORT_ERROR_DETAIL_KEYS
1344
+ );
1345
+ assertNonEmptyString(
1346
+ `${label} error.details.importCode`,
1347
+ details.importCode
1348
+ );
1310
1349
  }
1311
1350
  }
1312
- function assertPapiersTokenRevokeResponse(label, body, expectedTokenId) {
1313
- assertNoInternalResponseKeys(label, body);
1314
- const value = assertSchema(label, body, PAPIERS_SCHEMA_VERSIONS.tokenRevoke);
1315
- rejectUnexpectedKeys(label, value, TOKEN_REVOKE_RESPONSE_KEYS);
1351
+ var HIGHLIGHT_RESPONSE_KEYS = /* @__PURE__ */ new Set([
1352
+ "schemaVersion",
1353
+ "requestId",
1354
+ "highlightId"
1355
+ ]);
1356
+ function assertPapiersHighlightResponse(label, body) {
1357
+ const value = assertSchema(label, body, PAPIERS_SCHEMA_VERSIONS.highlight);
1358
+ rejectUnexpectedKeys(label, value, HIGHLIGHT_RESPONSE_KEYS);
1316
1359
  assertPublicRequestId(`${label} requestId`, value.requestId);
1317
- const tokenId = assertTokenId(`${label} tokenId`, value.tokenId);
1318
- if (tokenId !== expectedTokenId) {
1360
+ if (typeof value.highlightId !== "string" || !value.highlightId.startsWith("papiers://highlight/")) {
1319
1361
  throw new PapiersCliResponseValidationError(
1320
- `${label} did not echo the requested token id.`
1362
+ `${label} returned an invalid highlight identifier.`
1321
1363
  );
1322
1364
  }
1323
- assertBoolean(`${label} revoked`, value.revoked);
1324
1365
  }
1325
1366
  function assertPapiersErrorResponse(label, body, expectedHttpStatus) {
1326
1367
  assertNoInternalResponseKeys(label, body);
@@ -1438,28 +1479,6 @@ function validatePapiersReadResponse(label, body, options = {}) {
1438
1479
  );
1439
1480
  }
1440
1481
  }
1441
- function validateTokenSummary(label, rawToken) {
1442
- const token = assertRecord(label, rawToken);
1443
- rejectUnexpectedKeys(label, token, TOKEN_SUMMARY_KEYS);
1444
- assertTokenId(`${label} tokenId`, token.tokenId);
1445
- assertNonEmptyString(`${label} name`, token.name);
1446
- const scopes = assertArray(`${label} scopes`, token.scopes);
1447
- if (scopes.length === 0) {
1448
- throw new PapiersCliResponseValidationError(`${label} scopes was empty.`);
1449
- }
1450
- for (const [index, rawScope] of scopes.entries()) {
1451
- const scope = assertNonEmptyString(`${label} scope ${index}`, rawScope);
1452
- if (!PUBLIC_DATA_PLANE_SCOPE_SET.has(scope)) {
1453
- throw new PapiersCliResponseValidationError(
1454
- `${label} scope ${index} was not a public data-plane scope.`
1455
- );
1456
- }
1457
- }
1458
- assertTimestampString(`${label} createdAt`, token.createdAt);
1459
- validateOptionalTimestamp(`${label} expiresAt`, token.expiresAt);
1460
- validateOptionalTimestamp(`${label} revokedAt`, token.revokedAt);
1461
- validateOptionalTimestamp(`${label} lastUsedAt`, token.lastUsedAt);
1462
- }
1463
1482
  function validateSearchResults(label, results, options = {}) {
1464
1483
  const values = assertArray(`${label} results`, results);
1465
1484
  for (const [index, rawResult] of values.entries()) {
@@ -1807,7 +1826,7 @@ function validateOptionalListSearchMetadata(label, value, kind) {
1807
1826
  );
1808
1827
  }
1809
1828
  if (kind === "passage") {
1810
- return validatePassageListSearchMetadata(label, value);
1829
+ return validatePassageMetadata(label, value);
1811
1830
  }
1812
1831
  if (kind === "highlight") {
1813
1832
  return validateHighlightMetadata(label, value, assertDocId);
@@ -1821,7 +1840,7 @@ function validateOptionalListSearchMetadata(label, value, kind) {
1821
1840
  }
1822
1841
  return validateEquationReadMetadata(label, value);
1823
1842
  }
1824
- function validatePassageListSearchMetadata(label, value) {
1843
+ function validatePassageMetadata(label, value) {
1825
1844
  const metadata = assertRecord(label, value);
1826
1845
  rejectUnexpectedKeys(label, metadata, PASSAGE_METADATA_KEYS);
1827
1846
  const documentId = assertDocId(`${label}.documentId`, metadata.documentId);
@@ -1951,6 +1970,19 @@ function validatePassageReadSuccessItem(label, item, options) {
1951
1970
  const contentResult = validateReadContent(label, item, itemFormat, {
1952
1971
  expectedMaxCharsPerItem: options.expectedMaxCharsPerItem
1953
1972
  });
1973
+ const metadataRequested = options.expectedInclude?.has("metadata") ?? false;
1974
+ if (Object.prototype.hasOwnProperty.call(item, "metadata")) {
1975
+ if (!metadataRequested) {
1976
+ throw new PapiersCliResponseValidationError(
1977
+ `${label} returned metadata that was not requested.`
1978
+ );
1979
+ }
1980
+ validatePassageMetadata(`${label} metadata`, item.metadata);
1981
+ } else if (metadataRequested) {
1982
+ throw new PapiersCliResponseValidationError(
1983
+ `${label} did not return requested metadata.`
1984
+ );
1985
+ }
1954
1986
  const hasReadTruncatedWarning = validateOptionalReadWarnings(
1955
1987
  `${label} warnings`,
1956
1988
  item.warnings,
@@ -2383,7 +2415,8 @@ function validateReadItemError(label, value, kind, expectedFormat) {
2383
2415
  const sourceSupersededAllowed = error.code === DOCUMENT_READ_SOURCE_SUPERSEDED_CODE && (kind === "document" || kind === "passage" || kind === "note" || kind === "conversation");
2384
2416
  const contentLimitAllowed = error.code === CONVERSATION_READ_CONTENT_LIMIT_CODE && kind === "conversation";
2385
2417
  const requestLimitAllowed = error.code === CONVERSATION_READ_REQUEST_LIMIT_CODE && kind === "conversation";
2386
- if (error.code !== DOCUMENT_READ_NOT_FOUND_CODE && !sourceSupersededAllowed && !contentLimitAllowed && !requestLimitAllowed) {
2418
+ const staleReferenceKind = error.code === READ_STALE_REFERENCE_CODE && (kind === "passage" || kind === "equation") ? kind : null;
2419
+ if (error.code !== DOCUMENT_READ_NOT_FOUND_CODE && !sourceSupersededAllowed && !contentLimitAllowed && !requestLimitAllowed && !staleReferenceKind) {
2387
2420
  throw new PapiersCliResponseValidationError(
2388
2421
  `${label}.code was not a supported read error code.`
2389
2422
  );
@@ -2393,7 +2426,7 @@ function validateReadItemError(label, value, kind, expectedFormat) {
2393
2426
  `${label}.code was not supported for metadata reads.`
2394
2427
  );
2395
2428
  }
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;
2429
+ const expectedMessage = staleReferenceKind ? staleReferenceMessage(staleReferenceKind) : 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;
2397
2430
  if (error.message !== expectedMessage) {
2398
2431
  throw new PapiersCliResponseValidationError(
2399
2432
  `${label}.message was not a supported read error message.`
@@ -2920,14 +2953,6 @@ function assertReadIdentifier(label, value) {
2920
2953
  }
2921
2954
  return value;
2922
2955
  }
2923
- function assertTokenId(label, value) {
2924
- if (typeof value !== "string" || !isTokenUuid(value)) {
2925
- throw new PapiersCliResponseValidationError(
2926
- `${label} was not a token UUID.`
2927
- );
2928
- }
2929
- return value;
2930
- }
2931
2956
  function assertPublicRequestId(label, value) {
2932
2957
  if (typeof value !== "string" || !SAFE_REQUEST_ID_PATTERN.test(value)) {
2933
2958
  throw new PapiersCliResponseValidationError(
@@ -3015,7 +3040,7 @@ async function getCapabilities(config, fetchImpl) {
3015
3040
  config,
3016
3041
  fetchImpl,
3017
3042
  "/api/v1/papiers/capabilities",
3018
- { method: "GET", retryable: true }
3043
+ { method: "GET" }
3019
3044
  );
3020
3045
  assertPapiersCapabilitiesResponse("capabilities", body);
3021
3046
  return body;
@@ -3026,7 +3051,7 @@ async function listDocuments(config, fetchImpl, request) {
3026
3051
  config,
3027
3052
  fetchImpl,
3028
3053
  "/api/v1/papiers/list",
3029
- { method: "POST", body: request, retryable: true }
3054
+ { method: "POST", body: request }
3030
3055
  );
3031
3056
  assertPapiersListResponse("list", body, { expectedKind });
3032
3057
  return body;
@@ -3037,7 +3062,7 @@ async function searchDocuments(config, fetchImpl, request) {
3037
3062
  config,
3038
3063
  fetchImpl,
3039
3064
  "/api/v1/papiers/search",
3040
- { method: "POST", body: request, retryable: true }
3065
+ { method: "POST", body: request }
3041
3066
  );
3042
3067
  assertPapiersSearchResponse("search", body, request.query, expected);
3043
3068
  return body;
@@ -3047,7 +3072,7 @@ async function browseSearchResults(config, fetchImpl, request) {
3047
3072
  config,
3048
3073
  fetchImpl,
3049
3074
  "/api/v1/papiers/search/results",
3050
- { method: "POST", body: request, retryable: true }
3075
+ { method: "POST", body: request }
3051
3076
  );
3052
3077
  assertPapiersSearchResultsResponse("search results", body, request);
3053
3078
  return body;
@@ -3057,7 +3082,7 @@ async function readDocuments(config, fetchImpl, request) {
3057
3082
  config,
3058
3083
  fetchImpl,
3059
3084
  "/api/v1/papiers/read",
3060
- { method: "POST", body: request, retryable: true }
3085
+ { method: "POST", body: request }
3061
3086
  );
3062
3087
  const readKind = readKindForIdentifier(request.ids[0] ?? "");
3063
3088
  assertPapiersReadResponse("read", body, request.ids, {
@@ -3070,26 +3095,6 @@ async function readDocuments(config, fetchImpl, request) {
3070
3095
  });
3071
3096
  return body;
3072
3097
  }
3073
- async function listPapiersTokens(config, fetchImpl) {
3074
- const body = await requestPapiersApi(
3075
- config,
3076
- fetchImpl,
3077
- "/api/v1/papiers/tokens",
3078
- { method: "GET", retryable: true }
3079
- );
3080
- assertPapiersTokenListResponse("tokens list", body);
3081
- return body;
3082
- }
3083
- async function revokePapiersToken(config, fetchImpl, tokenId) {
3084
- const body = await requestPapiersApi(
3085
- config,
3086
- fetchImpl,
3087
- `/api/v1/papiers/tokens/${encodeURIComponent(tokenId)}/revoke`,
3088
- { method: "POST" }
3089
- );
3090
- assertPapiersTokenRevokeResponse("tokens revoke", body, tokenId);
3091
- return body;
3092
- }
3093
3098
  function expectedKindForListSearchRequest(request) {
3094
3099
  const kinds = request.kinds;
3095
3100
  if (kinds === void 0) {
@@ -3135,6 +3140,48 @@ function expectedKindsForSearchRequest(request) {
3135
3140
  const [onlyKind] = expectedKinds;
3136
3141
  return expectedKinds.size === 1 ? { expectedKind: onlyKind } : { expectedKinds };
3137
3142
  }
3143
+ async function importDocument(config, fetchImpl, request) {
3144
+ const body = await requestPapiersApi(
3145
+ config,
3146
+ fetchImpl,
3147
+ "/api/v1/papiers/imports",
3148
+ { method: "POST", body: request }
3149
+ );
3150
+ assertPapiersImportResponse("import", body);
3151
+ return body;
3152
+ }
3153
+ async function getImportStatus(config, fetchImpl, operationId) {
3154
+ const body = await requestPapiersApi(
3155
+ config,
3156
+ fetchImpl,
3157
+ `/api/v1/papiers/imports/${encodeURIComponent(operationId)}`,
3158
+ { method: "GET" }
3159
+ );
3160
+ assertPapiersImportResponse("import-status", body);
3161
+ return body;
3162
+ }
3163
+ async function highlightDocument(config, fetchImpl, request) {
3164
+ const body = await requestPapiersApi(
3165
+ config,
3166
+ fetchImpl,
3167
+ "/api/v1/papiers/highlights",
3168
+ // Outlasts the server's 10 s queue wait plus 20 s search, so its answer
3169
+ // arrives.
3170
+ { method: "POST", body: request, timeoutMs: 4e4 }
3171
+ );
3172
+ assertPapiersHighlightResponse("highlight", body);
3173
+ return body;
3174
+ }
3175
+ var FEED_PATH = "/api/v1/papiers/discover/feed";
3176
+ function addFeedLink(config, fetchImpl, link) {
3177
+ return requestPapiersApi(config, fetchImpl, FEED_PATH, {
3178
+ method: "POST",
3179
+ body: {
3180
+ schemaVersion: PAPIERS_SCHEMA_VERSIONS.feedLinkRequest,
3181
+ ...link
3182
+ }
3183
+ });
3184
+ }
3138
3185
  async function requestPapiersApi(config, fetchImpl, path, init) {
3139
3186
  if (!config.token) {
3140
3187
  throw new CliUsageError(
@@ -3164,10 +3211,13 @@ async function requestPapiersApiWithBearer(config, fetchImpl, bearerToken, path,
3164
3211
  headers["content-type"] = "application/json";
3165
3212
  requestInit.body = JSON.stringify(init.body);
3166
3213
  }
3167
- const response = await fetchWithRetry(fetchImpl, requestUrl, requestInit, {
3168
- retryable: init.retryable === true
3169
- });
3170
- const body = await parseResponseBody(response);
3214
+ const { response, text } = await fetchWithRetry(
3215
+ fetchImpl,
3216
+ requestUrl,
3217
+ requestInit,
3218
+ init.timeoutMs ?? REQUEST_TIMEOUT_MS
3219
+ );
3220
+ const body = parseResponseBody(text);
3171
3221
  if (!response.ok) {
3172
3222
  const errorEnvelope = extractErrorEnvelope(body, response.status);
3173
3223
  throw new PapiersCliApiError({
@@ -3179,44 +3229,45 @@ async function requestPapiersApiWithBearer(config, fetchImpl, bearerToken, path,
3179
3229
  }
3180
3230
  return body;
3181
3231
  }
3182
- async function fetchWithRetry(fetchImpl, requestUrl, requestInit, options) {
3183
- const maxAttempts = options.retryable ? MAX_REQUEST_ATTEMPTS : 1;
3232
+ async function fetchWithRetry(fetchImpl, requestUrl, requestInit, timeoutMs) {
3184
3233
  let attempt = 1;
3185
3234
  while (true) {
3186
3235
  let response;
3236
+ let text;
3187
3237
  try {
3188
3238
  response = await fetchImpl(requestUrl, {
3189
3239
  ...requestInit,
3190
- signal: AbortSignal.timeout(REQUEST_TIMEOUT_MS)
3240
+ signal: AbortSignal.timeout(timeoutMs)
3191
3241
  });
3242
+ text = await response.text();
3192
3243
  } catch (error) {
3193
- if (attempt >= maxAttempts) {
3244
+ if (attempt >= MAX_REQUEST_ATTEMPTS) {
3194
3245
  throw error;
3195
3246
  }
3196
3247
  await sleep(RETRY_BACKOFF_MS[attempt - 1] ?? 1e3);
3197
3248
  attempt += 1;
3198
3249
  continue;
3199
3250
  }
3200
- if (attempt < maxAttempts && RETRYABLE_STATUS_CODES.has(response.status)) {
3201
- await sleep(retryDelayMs(response, attempt));
3251
+ const delayMs = retryDelayMs(response, attempt);
3252
+ if (attempt < MAX_REQUEST_ATTEMPTS && RETRYABLE_STATUS_CODES.has(response.status) && delayMs <= MAX_RETRY_AFTER_MS) {
3253
+ await sleep(delayMs);
3202
3254
  attempt += 1;
3203
3255
  continue;
3204
3256
  }
3205
- return response;
3257
+ return { response, text };
3206
3258
  }
3207
3259
  }
3208
3260
  function retryDelayMs(response, attempt) {
3209
3261
  const retryAfterSeconds = Number(response.headers.get("retry-after") ?? "");
3210
3262
  if (Number.isFinite(retryAfterSeconds) && retryAfterSeconds > 0) {
3211
- return Math.min(retryAfterSeconds * 1e3, MAX_RETRY_AFTER_MS);
3263
+ return retryAfterSeconds * 1e3;
3212
3264
  }
3213
3265
  return RETRY_BACKOFF_MS[attempt - 1] ?? 1e3;
3214
3266
  }
3215
3267
  function sleep(ms) {
3216
3268
  return new Promise((resolveSleep) => setTimeout(resolveSleep, ms));
3217
3269
  }
3218
- async function parseResponseBody(response) {
3219
- const text = await response.text();
3270
+ function parseResponseBody(text) {
3220
3271
  if (!text) {
3221
3272
  return null;
3222
3273
  }
@@ -3241,6 +3292,30 @@ function formatApiErrorMessage(params) {
3241
3292
  return `Papiers API error ${params.status}.`;
3242
3293
  }
3243
3294
 
3295
+ // ../../src/shared/highlight-colors.ts
3296
+ var HIGHLIGHT_HEX_COLOR_REGEX = /^#[0-9a-f]{6}$/;
3297
+
3298
+ // ../../src/shared/papiers-public-api.ts
3299
+ var PAPIERS_PUBLIC_DATA_PLANE_SCOPES2 = [
3300
+ "library:metadata:read",
3301
+ "library:search",
3302
+ "content:search",
3303
+ "content:snippet:read",
3304
+ "content:read",
3305
+ "content:structure:read"
3306
+ ];
3307
+ var PAPIERS_WRITE_SCOPES = [
3308
+ "library:import",
3309
+ "library:highlight",
3310
+ "discover:feed"
3311
+ ];
3312
+ var PAPIERS_PUBLIC_SCOPES = [
3313
+ ...PAPIERS_PUBLIC_DATA_PLANE_SCOPES2,
3314
+ ...PAPIERS_WRITE_SCOPES
3315
+ ];
3316
+ var PAPIERS_HIGHLIGHT_MAX_SCOPE_PAGES = 20;
3317
+ var PAPIERS_HIGHLIGHT_MAX_PAGE = 1e4;
3318
+
3244
3319
  // src/filters.ts
3245
3320
  var FILTER_OPERATORS = [
3246
3321
  "eq",
@@ -3709,7 +3784,17 @@ var BOOLEAN_FLAGS = /* @__PURE__ */ new Set([
3709
3784
  "fail-fast",
3710
3785
  "version"
3711
3786
  ]);
3787
+ var HIGHLIGHT_VALUE_FLAGS = [
3788
+ "start",
3789
+ "end",
3790
+ "passage",
3791
+ "pages",
3792
+ "note",
3793
+ "color"
3794
+ ];
3712
3795
  var VALUE_FLAGS = /* @__PURE__ */ new Set([
3796
+ ...HIGHLIGHT_VALUE_FLAGS,
3797
+ "reason",
3713
3798
  "api-url",
3714
3799
  "cursor",
3715
3800
  "document-id",
@@ -3827,8 +3912,10 @@ COMMANDS
3827
3912
  list List your documents, highlights, notes, and conversations
3828
3913
  read Print a document, highlight, note, or conversation
3829
3914
  browse Page through an earlier search's results
3915
+ import Add a public PDF or webpage to your library
3916
+ highlight Highlight a passage in a saved document
3917
+ feed Add links to your Discover feed
3830
3918
  auth Sign in, check status, or sign out
3831
- tokens List or revoke your API tokens
3832
3919
  doctor Check your setup, connection, and what's available
3833
3920
  version Print the CLI version
3834
3921
 
@@ -3886,12 +3973,12 @@ USAGE
3886
3973
  papiers list --kind highlight|conversation [--since WHEN]
3887
3974
 
3888
3975
  OPTIONS
3889
- --kind KIND document (default) \xB7 passage \xB7 highlight \xB7 conversation \xB7 equation \xB7 note.
3976
+ --kind KIND document (default) \xB7 highlight \xB7 conversation \xB7 note.
3890
3977
  --filter FIELD.OP=VALUE narrow document results \u2014 repeatable, up to 25. document kind only.
3891
3978
  e.g. --filter lastEngagedAt.gte=30d --filter annotated=true. Fields/ops: help --all.
3892
3979
  --order-by KEY addedAt:desc (default) or lastEngagedAt:desc. document kind only.
3893
3980
  --document-id ID scope to documents: doc_\u2026 (comma-separated or repeated), or @-/@file.
3894
- document and highlight only.
3981
+ document and highlight only; highlight also takes papiers://item/\u2026 locators.
3895
3982
  --since WHEN only items at or after an ISO 8601 time, or an Nd shorthand (e.g. 30d).
3896
3983
  highlight and conversation only.
3897
3984
  --limit N max results, 1\u201350.
@@ -3916,7 +4003,7 @@ OPTIONS
3916
4003
  highlight, conversation: metadata|plain. note: markdown|plain.
3917
4004
  equation: metadata.
3918
4005
  --include SECTION extra sections, comma-separated: metadata, source, locations.
3919
- document only (highlight/conversation/equation/note: metadata only; passage: none).
4006
+ document only (passage/highlight/conversation/equation/note: metadata only).
3920
4007
  --pages RANGE restrict a document read to pages, e.g. 1,3-5. document only.
3921
4008
  --max-chars-per-item N truncate each item's content to N chars, 1\u2013200000.
3922
4009
  --cursor CURSOR continue one long document or note \u2014 use items[].nextCursor from the
@@ -3959,13 +4046,54 @@ OPTIONS
3959
4046
  --force when signing in, replace an already-saved token.
3960
4047
 
3961
4048
  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.
4049
+ import: `papiers import \u2014 add a public PDF or webpage to your library.
4050
+
4051
+ USAGE
4052
+ papiers import URL [--json]
4053
+ papiers import-status OPERATION_ID [--json]
4054
+
4055
+ Needs a token created with "Allow document imports" (Settings -> API tokens).
4056
+ Import only sources the user approved. An import is queued, not finished:
4057
+ check import-status until it is ready or failed; any other status means it
4058
+ is still in progress. Ready means papiers read works on its documentId now.
4059
+ A status is as of that check: reprocessing or trashing the document can
4060
+ change it later.`,
4061
+ feed: `papiers feed \u2014 add links to your own Discover feed.
3963
4062
 
3964
4063
  USAGE
3965
- papiers tokens list [--json]
3966
- papiers tokens revoke TOKEN_ID [--json]
4064
+ papiers feed add URL [--reason TEXT] [--json]
3967
4065
 
3968
- Create and name tokens in Papiers (Settings -> API tokens). Revoking is permanent.`,
4066
+ Needs a token created with "Allow adding to your Discover feed" (Settings ->
4067
+ API tokens). A link shows in the user's Discover feed, labeled with the
4068
+ token's name and your reason, once Papiers has prepared it (usually within
4069
+ minutes); it never adds to their library. Give a short reason in your own
4070
+ words: why this user would want it. Sending a link again updates its reason.
4071
+ Links must be public pages: not paywalled, with no sign-in or private token.
4072
+ The user's plan sets how many new links it takes; past that, the answer says
4073
+ when to try again. The answer says whether the link is preparing, ready or
4074
+ failed; send it again to see how it is now.`,
4075
+ highlight: `papiers highlight \u2014 highlight a passage in a saved PDF or webpage.
4076
+
4077
+ USAGE
4078
+ papiers highlight DOCUMENT_ID --start TEXT [--end TEXT]
4079
+ [--passage PASSAGE_REF | --pages N[-M]]
4080
+ [--note TEXT] [--color #rrggbb] [--json]
4081
+
4082
+ Needs a token created with "Allow highlights" (Settings -> API tokens).
4083
+ DOCUMENT_ID is a doc_ ID or papiers://item/... locator. In a PDF, name what you
4084
+ read: --passage is the reference of a search result in this document
4085
+ (doc_\u2026#p\u2026.\u2026), --pages the page or pages (3, 3-4 or 3,5) you read it on. The
4086
+ passage is only looked for there; a webpage is searched whole and takes neither.
4087
+ --start is the passage's first words (or all of a short passage); --end its
4088
+ last words, for a longer passage or one crossing a formula. The highlight runs
4089
+ from --start through --end, so formulas and footnote marks between need not be
4090
+ quoted; if either repeats within reach (that page and the next, a few
4091
+ paragraphs), add words to it. Quote both as you read them; punctuation, hyphens,
4092
+ spacing and formatting don't matter. Keep math out of --start and --end, even
4093
+ a single symbol like V or \u03B2t: what you read spells it differently from the
4094
+ page, so start and end on plain words. If the passage appears more than once,
4095
+ add words until it is unique. Highlight only passages the user asked for or
4096
+ approved.`,
3969
4097
  doctor: `papiers doctor \u2014 check your setup, connection, and what's available.
3970
4098
 
3971
4099
  USAGE
@@ -3981,10 +4109,18 @@ USAGE
3981
4109
  var REFERENCE = `REFERENCE \u2014 IDs, piping, pagination, and limits.
3982
4110
 
3983
4111
  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.
4112
+ doc_ document hl_ highlight cnv_ conversation
4113
+ nte_ note srch_ a saved search ranking (for browse)
4114
+ On read, the kind \u2014 and which --format/--include apply \u2014 is read from the ID.
4115
+
4116
+ PASSAGES AND EQUATIONS
4117
+ A passage or equation is a place in a document plus a fingerprint of its text:
4118
+ doc_<id>#p12.3f9c01aa passage 12 of that document
4119
+ doc_<id>#enode_812.77d0e2b1 an equation in that document
4120
+ It keeps working while that text is unchanged. If the document is processed
4121
+ again and the text changed, you get stale_reference: search or read the
4122
+ document again. Quote references in the shell ('doc_\u2026#p12.3f9c01aa'): some
4123
+ shells treat # specially.
3988
4124
 
3989
4125
  RESOURCE LOCATORS
3990
4126
  papiers://note/<uuid> papiers://highlight/<uuid>
@@ -4041,11 +4177,12 @@ var SUGGESTIBLE_COMMANDS = [
4041
4177
  "auth",
4042
4178
  "doctor",
4043
4179
  "capabilities",
4044
- "tokens",
4045
4180
  "list",
4046
4181
  "search",
4047
4182
  "browse",
4048
4183
  "read",
4184
+ "import",
4185
+ "highlight",
4049
4186
  "version",
4050
4187
  "help"
4051
4188
  ];
@@ -4098,7 +4235,8 @@ function parseCliArgs(argv) {
4098
4235
  if (parsed.booleanFlags.has("all")) {
4099
4236
  return { type: "help", all: true, ...global };
4100
4237
  }
4101
- const topic = command === "help" ? rest[0] : command;
4238
+ const named = command === "help" ? rest[0] : command;
4239
+ const topic = named === "import-status" ? "import" : named;
4102
4240
  if (topic && HELP_TOPIC_NAMES.includes(topic)) {
4103
4241
  return { type: "help", topic, ...global };
4104
4242
  }
@@ -4125,14 +4263,72 @@ function parseCliArgs(argv) {
4125
4263
  );
4126
4264
  assertNoExtraPositionals(command, rest);
4127
4265
  return { type: "capabilities", ...global };
4128
- case "tokens":
4129
- assertAllowedFlags(parsed, NO_COMMAND_VALUE_FLAGS, "tokens");
4130
- return parseTokensCommand(rest, parsed, global);
4266
+ case "import":
4267
+ case "import-status": {
4268
+ assertAllowedFlags(parsed, NO_COMMAND_VALUE_FLAGS, command);
4269
+ assertAllowedBooleanFlags(parsed, NO_COMMAND_BOOLEAN_FLAGS, command);
4270
+ const [target, ...extra] = rest;
4271
+ assertNoExtraPositionals(command, extra);
4272
+ if (!target) {
4273
+ throw new CliUsageError(
4274
+ command === "import" ? "papiers import requires a URL." : "papiers import-status requires an operation ID."
4275
+ );
4276
+ }
4277
+ return command === "import" ? { type: "import", ...global, url: target } : { type: "import-status", ...global, operationId: target };
4278
+ }
4279
+ case "feed":
4280
+ return parseFeedCommand(rest, parsed, global);
4281
+ case "highlight": {
4282
+ assertAllowedFlags(
4283
+ parsed,
4284
+ /* @__PURE__ */ new Set([...GLOBAL_VALUE_FLAGS, ...HIGHLIGHT_VALUE_FLAGS]),
4285
+ command
4286
+ );
4287
+ assertAllowedBooleanFlags(parsed, NO_COMMAND_BOOLEAN_FLAGS, command);
4288
+ const [documentId, ...extra] = rest;
4289
+ assertNoExtraPositionals(command, extra);
4290
+ const start = singleFlagValue(parsed, "start");
4291
+ if (!documentId || !start) {
4292
+ throw new CliUsageError(
4293
+ "papiers highlight requires a document ID and --start."
4294
+ );
4295
+ }
4296
+ const passageId = singleFlagValue(parsed, "passage");
4297
+ const pagesFlag = singleFlagValue(parsed, "pages");
4298
+ if (passageId !== void 0 && pagesFlag !== void 0) {
4299
+ throw new CliUsageError("Give --passage or --pages, not both.");
4300
+ }
4301
+ const color = singleFlagValue(parsed, "color");
4302
+ if (color !== void 0 && !HIGHLIGHT_HEX_COLOR_REGEX.test(color)) {
4303
+ throw new CliUsageError(
4304
+ "--color must be a lowercase #rrggbb hex color, like #fde68a."
4305
+ );
4306
+ }
4307
+ const optional = (name) => {
4308
+ const value = singleFlagValue(parsed, name);
4309
+ return value === void 0 ? {} : { [name]: value };
4310
+ };
4311
+ return {
4312
+ type: "highlight",
4313
+ ...global,
4314
+ documentId,
4315
+ start,
4316
+ ...optional("end"),
4317
+ ...passageId === void 0 ? {} : { passageId },
4318
+ ...pagesFlag === void 0 ? {} : { pages: parsePages(pagesFlag) },
4319
+ ...optional("note"),
4320
+ ...optional("color")
4321
+ };
4322
+ }
4131
4323
  case "list": {
4132
4324
  assertAllowedFlags(parsed, LIST_VALUE_FLAGS, "list");
4133
4325
  assertAllowedBooleanFlags(parsed, IDS_PIPELINE_BOOLEAN_FLAGS, "list");
4134
4326
  assertNoExtraPositionals(command, rest);
4135
- const listDocumentIds = parseDocumentIdFlagValues(parsed, "list");
4327
+ const listDocumentIds = parseDocumentIdFlagValues(
4328
+ parsed,
4329
+ "list",
4330
+ parseListKind(parsed) === "highlight"
4331
+ );
4136
4332
  const listOutput = parseIdsOutput(parsed, "list");
4137
4333
  return {
4138
4334
  type: "list",
@@ -4226,33 +4422,31 @@ function parsePrettyOutput(parsed, idsOnly) {
4226
4422
  }
4227
4423
  return true;
4228
4424
  }
4229
- function parseTokensCommand(rest, parsed, global) {
4230
- const [subcommand, tokenId, ...extra] = rest;
4425
+ function parseFeedCommand(rest, parsed, global) {
4426
+ const [subcommand, target, ...extra] = rest;
4427
+ const name = `feed ${subcommand ?? ""}`.trim();
4428
+ assertAllowedBooleanFlags(parsed, NO_COMMAND_BOOLEAN_FLAGS, name);
4231
4429
  switch (subcommand) {
4232
- case "list":
4233
- assertAllowedBooleanFlags(
4430
+ case "add": {
4431
+ assertAllowedFlags(
4234
4432
  parsed,
4235
- NO_COMMAND_BOOLEAN_FLAGS,
4236
- "tokens list"
4433
+ /* @__PURE__ */ new Set([...GLOBAL_VALUE_FLAGS, "reason"]),
4434
+ name
4237
4435
  );
4238
- assertNoExtraPositionals("tokens list", rest.slice(1));
4239
- return { type: "tokens-list", ...global };
4240
- case "revoke":
4241
- assertAllowedBooleanFlags(
4242
- parsed,
4243
- NO_COMMAND_BOOLEAN_FLAGS,
4244
- "tokens revoke"
4245
- );
4246
- if (!tokenId) {
4247
- throw new CliUsageError("papiers tokens revoke requires a token UUID.");
4248
- }
4249
- assertNoExtraPositionals("tokens revoke", extra);
4250
- if (!isUuid(tokenId)) {
4251
- throw new CliUsageError("papiers tokens revoke requires a token UUID.");
4252
- }
4253
- return { type: "tokens-revoke", ...global, tokenId };
4436
+ assertNoExtraPositionals(name, extra);
4437
+ if (!target) throw new CliUsageError("papiers feed add requires a URL.");
4438
+ const reason = singleFlagValue(parsed, "reason");
4439
+ return {
4440
+ type: "feed-add",
4441
+ ...global,
4442
+ url: target,
4443
+ ...reason !== void 0 ? { reason } : {}
4444
+ };
4445
+ }
4254
4446
  default:
4255
- throw new CliUsageError("Supported tokens commands: list, revoke.");
4447
+ throw new CliUsageError(
4448
+ "papiers feed takes add. Run papiers feed --help."
4449
+ );
4256
4450
  }
4257
4451
  }
4258
4452
  function parseAuthCommand(rest, parsed, global) {
@@ -4357,7 +4551,7 @@ function buildReadRequest(parsed) {
4357
4551
  };
4358
4552
  }
4359
4553
  function buildListRequest(parsed, documentIds) {
4360
- const kind = parseListSearchKind(parsed, "list");
4554
+ const kind = parseListKind(parsed);
4361
4555
  const since = parseSinceFlag(parsed, kind, "list");
4362
4556
  if (kind !== "document") {
4363
4557
  if (documentIds?.length && kind !== "highlight") {
@@ -4495,7 +4689,7 @@ function parseSinceFlag(parsed, kind, command) {
4495
4689
  }
4496
4690
  return new Date(parsedMs).toISOString();
4497
4691
  }
4498
- function parseDocumentIdFlagValues(parsed, command) {
4692
+ function parseDocumentIdFlagValues(parsed, command, acceptsItemLocators = false) {
4499
4693
  const rawDocumentIds = parsed.flags.get("document-id");
4500
4694
  if (!rawDocumentIds?.length) {
4501
4695
  return void 0;
@@ -4513,9 +4707,10 @@ function parseDocumentIdFlagValues(parsed, command) {
4513
4707
  `papiers ${command} --document-id file references must include a file path after @.`
4514
4708
  );
4515
4709
  }
4516
- if (documentIds.some((id) => !isDocumentIdSource(id) && !isDocumentPublicId(id))) {
4710
+ const isDocumentId = acceptsItemLocators ? isDocumentReadIdentifier : isDocumentPublicId;
4711
+ if (documentIds.some((id) => !isDocumentIdSource(id) && !isDocumentId(id))) {
4517
4712
  throw new CliUsageError(
4518
- `papiers ${command} --document-id currently accepts only doc_* IDs.`
4713
+ acceptsItemLocators ? `papiers ${command} --document-id accepts only doc_* IDs or papiers://item locators.` : `papiers ${command} --document-id currently accepts only doc_* IDs.`
4519
4714
  );
4520
4715
  }
4521
4716
  const explicitDocumentIdCount = documentIds.filter(
@@ -4649,7 +4844,13 @@ function validateReadOptionsForKind(parsed, kind, format) {
4649
4844
  "Public passage read supports --format markdown or --format plain."
4650
4845
  );
4651
4846
  }
4652
- rejectReadFlagsForKind(parsed, "passage", ["include", "pages", "cursor"]);
4847
+ const includes = parseReadInclude(parsed);
4848
+ if (includes?.some((include) => include !== "metadata")) {
4849
+ throw new CliUsageError(
4850
+ "Public passage read supports only --include metadata."
4851
+ );
4852
+ }
4853
+ rejectReadFlagsForKind(parsed, "passage", ["pages", "cursor"]);
4653
4854
  return;
4654
4855
  }
4655
4856
  if (kind === "highlight") {
@@ -4834,19 +5035,24 @@ function parseGlobalOptions(parsed) {
4834
5035
  apiBaseUrl: singleFlagValue(parsed, "api-url")
4835
5036
  };
4836
5037
  }
4837
- function parseListSearchKind(parsed, command) {
5038
+ function parseListKind(parsed) {
4838
5039
  const kind = singleFlagValue(parsed, "kind") ?? "document";
4839
5040
  const normalized = normalizeCliPublicKind(kind);
5041
+ if (normalized === "passage" || normalized === "equation") {
5042
+ throw new CliUsageError(
5043
+ `papiers list does not list ${normalized}s. Read a document (papiers read doc_\u2026) to page through its ${normalized}s in order, or search for them.`
5044
+ );
5045
+ }
4840
5046
  if (normalized) {
4841
5047
  return normalized;
4842
5048
  }
4843
5049
  if (kind.trim().toLowerCase() === "all") {
4844
5050
  throw new CliUsageError(
4845
- `papiers ${command} does not support --kind all here; multi-kind requests are available via papiers search --kind all.`
5051
+ "papiers list does not support --kind all here; multi-kind requests are available via papiers search --kind all."
4846
5052
  );
4847
5053
  }
4848
5054
  throw new CliUsageError(
4849
- `papiers ${command} supports --kind document, passage, highlight, conversation, equation, or note; received "${kind}".${didYouMeanKind(kind)}`
5055
+ `papiers list supports --kind document, highlight, conversation, or note; received "${kind}".${didYouMeanKind(kind)}`
4850
5056
  );
4851
5057
  }
4852
5058
  function normalizeCliPublicKind(value) {
@@ -5025,11 +5231,6 @@ function singleFlagValue(parsed, name) {
5025
5231
  }
5026
5232
  return values[0];
5027
5233
  }
5028
- function isUuid(value) {
5029
- return /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu.test(
5030
- value
5031
- );
5032
- }
5033
5234
  function splitCommaSeparatedValues(rawValue) {
5034
5235
  return rawValue.split(",").map((value) => value.trim()).filter(Boolean);
5035
5236
  }
@@ -5045,6 +5246,26 @@ function pruneUndefined(value) {
5045
5246
  Object.entries(value).filter(([, entry]) => entry !== void 0)
5046
5247
  );
5047
5248
  }
5249
+ function parsePages(value) {
5250
+ const pages = [];
5251
+ for (const part of value.split(",")) {
5252
+ const match = /^\s*(\d+)\s*(?:-\s*(\d+)\s*)?$/.exec(part);
5253
+ const first = match ? Number(match[1]) : Number.NaN;
5254
+ const last = match?.[2] ? Number(match[2]) : first;
5255
+ if (!(first >= 1 && first <= last && last <= PAPIERS_HIGHLIGHT_MAX_PAGE)) {
5256
+ throw new CliUsageError(
5257
+ `--pages must be a page, a range like 3-4, or a list like 3,5, of pages up to ${PAPIERS_HIGHLIGHT_MAX_PAGE}.`
5258
+ );
5259
+ }
5260
+ if (pages.length + (last - first + 1) > PAPIERS_HIGHLIGHT_MAX_SCOPE_PAGES) {
5261
+ throw new CliUsageError(
5262
+ `--pages can name at most ${PAPIERS_HIGHLIGHT_MAX_SCOPE_PAGES} pages.`
5263
+ );
5264
+ }
5265
+ for (let page = first; page <= last; page++) pages.push(page);
5266
+ }
5267
+ return pages;
5268
+ }
5048
5269
 
5049
5270
  // src/capabilities.ts
5050
5271
  var CAPABILITIES_ACCOUNT_KEYS = [
@@ -5074,11 +5295,11 @@ function assertSupportedDocumentCapabilities(value) {
5074
5295
  assertNonEmptyString2("capabilities account.userId", account.userId);
5075
5296
  assertExactString("capabilities account.profile", account.profile, "pat-v1");
5076
5297
  const scopes = assertStringArray("capabilities scopes", capabilities.scopes);
5077
- assertSameStringSet(
5078
- "capabilities scopes",
5079
- scopes,
5080
- PAPIERS_PUBLIC_DATA_PLANE_SCOPES
5081
- );
5298
+ if (!PAPIERS_PUBLIC_DATA_PLANE_SCOPES.every((s) => scopes.includes(s))) {
5299
+ throw new Error(
5300
+ `capabilities scopes was ${scopes.join(", ") || "empty"}; expected every public read scope.`
5301
+ );
5302
+ }
5082
5303
  const searchKinds = assertStringArray(
5083
5304
  "capabilities searchKinds",
5084
5305
  capabilities.searchKinds
@@ -5740,22 +5961,45 @@ async function runCli(argv, dependencies = {}) {
5740
5961
  );
5741
5962
  return 0;
5742
5963
  }
5743
- case "tokens-list":
5744
- writeTokenListOutput(
5964
+ case "feed-add":
5965
+ writeJson(
5966
+ stdout,
5967
+ await addFeedLink(config, resolveFetch(dependencies.fetch), {
5968
+ url: command.url,
5969
+ ...command.reason !== void 0 ? { reason: command.reason } : {}
5970
+ })
5971
+ );
5972
+ return 0;
5973
+ case "import":
5974
+ writeJson(
5745
5975
  stdout,
5746
- await listPapiersTokens(config, resolveFetch(dependencies.fetch)),
5747
- command.json
5976
+ await importDocument(config, resolveFetch(dependencies.fetch), {
5977
+ schemaVersion: PAPIERS_SCHEMA_VERSIONS.importRequest,
5978
+ url: command.url,
5979
+ submissionId: randomUUID2()
5980
+ })
5748
5981
  );
5749
5982
  return 0;
5750
- case "tokens-revoke":
5751
- writeTokenRevokeOutput(
5983
+ case "highlight": {
5984
+ const { type: _, json: __, apiBaseUrl: ___, ...highlight } = command;
5985
+ writeJson(
5752
5986
  stdout,
5753
- await revokePapiersToken(
5987
+ await highlightDocument(config, resolveFetch(dependencies.fetch), {
5988
+ schemaVersion: PAPIERS_SCHEMA_VERSIONS.highlightRequest,
5989
+ ...highlight,
5990
+ referenceId: randomUUID2()
5991
+ })
5992
+ );
5993
+ return 0;
5994
+ }
5995
+ case "import-status":
5996
+ writeJson(
5997
+ stdout,
5998
+ await getImportStatus(
5754
5999
  config,
5755
6000
  resolveFetch(dependencies.fetch),
5756
- command.tokenId
5757
- ),
5758
- command.json
6001
+ command.operationId
6002
+ )
5759
6003
  );
5760
6004
  return 0;
5761
6005
  case "list":
@@ -6016,50 +6260,6 @@ function writeAuthInspectOutput(stdout, output, json) {
6016
6260
  `
6017
6261
  );
6018
6262
  }
6019
- function writeTokenListOutput(stdout, output, json) {
6020
- if (json) {
6021
- writeJson(stdout, output);
6022
- return;
6023
- }
6024
- if (output.tokens.length === 0) {
6025
- stdout.write(
6026
- `No active Papiers tokens found. requestId=${output.requestId}
6027
- `
6028
- );
6029
- return;
6030
- }
6031
- const lines = [
6032
- `Papiers tokens requestId=${output.requestId}`,
6033
- ...output.tokens.map(
6034
- (token) => [
6035
- token.tokenId,
6036
- token.name,
6037
- `scopes=${token.scopes.join(",")}`,
6038
- `created=${token.createdAt}`,
6039
- `expires=${token.expiresAt ?? "never"}`,
6040
- `lastUsed=${token.lastUsedAt ?? "never"}`
6041
- ].join(" | ")
6042
- )
6043
- ];
6044
- stdout.write(`${redactSensitiveText(lines.join("\n"))}
6045
- `);
6046
- }
6047
- function writeTokenRevokeOutput(stdout, output, json) {
6048
- if (json) {
6049
- writeJson(stdout, output);
6050
- return;
6051
- }
6052
- const state = output.revoked ? "revoked" : "already revoked";
6053
- stdout.write(
6054
- `${redactSensitiveText(
6055
- [
6056
- `Token ${output.tokenId} ${state}. requestId=${output.requestId}`,
6057
- "Local auth files were not changed; run papiers auth logout if this was your configured token."
6058
- ].join("\n")
6059
- )}
6060
- `
6061
- );
6062
- }
6063
6263
  async function runAuthInspect(params) {
6064
6264
  const capabilities = await getCapabilities(params.config, params.fetchImpl);
6065
6265
  assertCliSupportedCapabilities(capabilities);
@@ -6613,9 +6813,9 @@ function validateExpandedReadRequest(request, ids, requestedReadKind) {
6613
6813
  "Public passage read supports --format markdown or --format plain."
6614
6814
  );
6615
6815
  }
6616
- if (request.include?.length) {
6816
+ if (request.include?.some((include) => include !== "metadata")) {
6617
6817
  throw new CliUsageError(
6618
- "Public passage read does not support --include."
6818
+ "Public passage read supports only --include metadata."
6619
6819
  );
6620
6820
  }
6621
6821
  if (request.scope?.pages || request.scope?.sectionId) {