@ncukondo/search-hub 0.12.1 → 0.12.2
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/_virtual/_commonjsHelpers.js +30 -0
- package/dist/_virtual/_commonjsHelpers.js.map +1 -0
- package/dist/_virtual/aliases.js +5 -0
- package/dist/_virtual/aliases.js.map +1 -0
- package/dist/_virtual/attributes.js +5 -0
- package/dist/_virtual/attributes.js.map +1 -0
- package/dist/_virtual/back.js +5 -0
- package/dist/_virtual/back.js.map +1 -0
- package/dist/_virtual/comment.js +5 -0
- package/dist/_virtual/comment.js.map +1 -0
- package/dist/_virtual/compile.js +5 -0
- package/dist/_virtual/compile.js.map +1 -0
- package/dist/_virtual/compile2.js +5 -0
- package/dist/_virtual/compile2.js.map +1 -0
- package/dist/_virtual/decode-data-html.js +5 -0
- package/dist/_virtual/decode-data-html.js.map +1 -0
- package/dist/_virtual/decode-data-xml.js +5 -0
- package/dist/_virtual/decode-data-xml.js.map +1 -0
- package/dist/_virtual/decode.js +5 -0
- package/dist/_virtual/decode.js.map +1 -0
- package/dist/_virtual/decode_codepoint.js +5 -0
- package/dist/_virtual/decode_codepoint.js.map +1 -0
- package/dist/_virtual/encode-html.js +5 -0
- package/dist/_virtual/encode-html.js.map +1 -0
- package/dist/_virtual/encode.js +5 -0
- package/dist/_virtual/encode.js.map +1 -0
- package/dist/_virtual/escape.js +5 -0
- package/dist/_virtual/escape.js.map +1 -0
- package/dist/_virtual/feeds.js +5 -0
- package/dist/_virtual/feeds.js.map +1 -0
- package/dist/_virtual/filters.js +5 -0
- package/dist/_virtual/filters.js.map +1 -0
- package/dist/_virtual/foreignNames.js +5 -0
- package/dist/_virtual/foreignNames.js.map +1 -0
- package/dist/_virtual/general.js +5 -0
- package/dist/_virtual/general.js.map +1 -0
- package/dist/_virtual/he.js +5 -0
- package/dist/_virtual/he.js.map +1 -0
- package/dist/_virtual/helpers.js +5 -0
- package/dist/_virtual/helpers.js.map +1 -0
- package/dist/_virtual/html.js +5 -0
- package/dist/_virtual/html.js.map +1 -0
- package/dist/_virtual/index.js +6 -0
- package/dist/_virtual/index.js.map +1 -0
- package/dist/_virtual/index10.js +5 -0
- package/dist/_virtual/index10.js.map +1 -0
- package/dist/_virtual/index11.js +5 -0
- package/dist/_virtual/index11.js.map +1 -0
- package/dist/_virtual/index2.js +5 -0
- package/dist/_virtual/index2.js.map +1 -0
- package/dist/_virtual/index3.js +5 -0
- package/dist/_virtual/index3.js.map +1 -0
- package/dist/_virtual/index4.js +5 -0
- package/dist/_virtual/index4.js.map +1 -0
- package/dist/_virtual/index5.js +7 -0
- package/dist/_virtual/index5.js.map +1 -0
- package/dist/_virtual/index6.js +5 -0
- package/dist/_virtual/index6.js.map +1 -0
- package/dist/_virtual/index7.js +5 -0
- package/dist/_virtual/index7.js.map +1 -0
- package/dist/_virtual/index8.js +5 -0
- package/dist/_virtual/index8.js.map +1 -0
- package/dist/_virtual/index9.js +5 -0
- package/dist/_virtual/index9.js.map +1 -0
- package/dist/_virtual/legacy.js +5 -0
- package/dist/_virtual/legacy.js.map +1 -0
- package/dist/_virtual/manipulation.js +5 -0
- package/dist/_virtual/manipulation.js.map +1 -0
- package/dist/_virtual/matcher.js +5 -0
- package/dist/_virtual/matcher.js.map +1 -0
- package/dist/_virtual/node.js +5 -0
- package/dist/_virtual/node.js.map +1 -0
- package/dist/_virtual/node2.js +5 -0
- package/dist/_virtual/node2.js.map +1 -0
- package/dist/_virtual/parse.js +5 -0
- package/dist/_virtual/parse.js.map +1 -0
- package/dist/_virtual/parse2.js +5 -0
- package/dist/_virtual/parse2.js.map +1 -0
- package/dist/_virtual/pseudos.js +5 -0
- package/dist/_virtual/pseudos.js.map +1 -0
- package/dist/_virtual/querying.js +5 -0
- package/dist/_virtual/querying.js.map +1 -0
- package/dist/_virtual/sort.js +5 -0
- package/dist/_virtual/sort.js.map +1 -0
- package/dist/_virtual/stringify.js +5 -0
- package/dist/_virtual/stringify.js.map +1 -0
- package/dist/_virtual/subselects.js +5 -0
- package/dist/_virtual/subselects.js.map +1 -0
- package/dist/_virtual/text.js +5 -0
- package/dist/_virtual/text.js.map +1 -0
- package/dist/_virtual/traversal.js +5 -0
- package/dist/_virtual/traversal.js.map +1 -0
- package/dist/_virtual/type.js +5 -0
- package/dist/_virtual/type.js.map +1 -0
- package/dist/_virtual/valid.js +5 -0
- package/dist/_virtual/valid.js.map +1 -0
- package/dist/_virtual/void-tag.js +5 -0
- package/dist/_virtual/void-tag.js.map +1 -0
- package/dist/cli/commands/fulltext/attach.js +1 -1
- package/dist/cli/commands/fulltext/attach.js.map +1 -1
- package/dist/cli/commands/fulltext/check.d.ts +1 -2
- package/dist/cli/commands/fulltext/check.d.ts.map +1 -1
- package/dist/cli/commands/fulltext/check.js +4 -2
- package/dist/cli/commands/fulltext/check.js.map +1 -1
- package/dist/cli/commands/fulltext/convert.d.ts.map +1 -1
- package/dist/cli/commands/fulltext/convert.js +8 -8
- package/dist/cli/commands/fulltext/convert.js.map +1 -1
- package/dist/cli/commands/fulltext/fetch.d.ts.map +1 -1
- package/dist/cli/commands/fulltext/fetch.js +10 -6
- package/dist/cli/commands/fulltext/fetch.js.map +1 -1
- package/dist/cli/commands/fulltext/index.d.ts.map +1 -1
- package/dist/cli/commands/fulltext/index.js +2 -0
- package/dist/cli/commands/fulltext/index.js.map +1 -1
- package/dist/cli/commands/fulltext/init.d.ts.map +1 -1
- package/dist/cli/commands/fulltext/init.js +6 -5
- package/dist/cli/commands/fulltext/init.js.map +1 -1
- package/dist/cli/commands/fulltext/pending.d.ts +1 -1
- package/dist/cli/commands/fulltext/pending.d.ts.map +1 -1
- package/dist/cli/commands/fulltext/pending.js +4 -2
- package/dist/cli/commands/fulltext/pending.js.map +1 -1
- package/dist/cli/commands/fulltext/status.d.ts.map +1 -1
- package/dist/cli/commands/fulltext/status.js +4 -2
- package/dist/cli/commands/fulltext/status.js.map +1 -1
- package/dist/cli/commands/fulltext/sync.d.ts.map +1 -1
- package/dist/cli/commands/fulltext/sync.js +6 -2
- package/dist/cli/commands/fulltext/sync.js.map +1 -1
- package/dist/cli/commands/review/types.d.ts +1 -1
- package/dist/cli/commands/review/types.d.ts.map +1 -1
- package/dist/cli/commands/review/types.js.map +1 -1
- package/dist/config/schema.d.ts +2 -0
- package/dist/config/schema.d.ts.map +1 -1
- package/dist/config/schema.js +6 -0
- package/dist/config/schema.js.map +1 -1
- package/dist/{fulltext → integration}/attach-shared.d.ts +2 -2
- package/dist/integration/attach-shared.d.ts.map +1 -0
- package/dist/integration/attach-shared.js.map +1 -0
- package/dist/integration/fulltext-attach.js +1 -1
- package/dist/integration/fulltext-attach.js.map +1 -1
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/citation-key.js +1 -1
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/citation-key.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/arxiv-html-parser.js +434 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/arxiv-html-parser.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/index.js +93 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/index.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js +1060 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/jats-parser.js.map +1 -0
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/convert/markdown-writer.js +146 -117
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/convert/markdown-writer.js.map +1 -0
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/discovery/arxiv.js +8 -1
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/arxiv.js.map +1 -0
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/discovery/core.js +6 -3
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/core.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/index.js +139 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/index.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/ncbi-id-converter.js +46 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/ncbi-id-converter.js.map +1 -0
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/discovery/pmc.js +8 -4
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/pmc.js.map +1 -0
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/discovery/unpaywall.js +43 -9
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/discovery/unpaywall.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/arxiv-html.js +48 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/arxiv-html.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/downloader.js +64 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/downloader.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/orchestrator.js +236 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/orchestrator.js.map +1 -0
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/download/pmc-xml.js +2 -1
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/download/pmc-xml.js.map +1 -0
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/meta.js +15 -10
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/meta.js.map +1 -0
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/paths.js.map +1 -0
- package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/readme.js +8 -4
- package/dist/node_modules/@ncukondo/academic-fulltext/dist/readme.js.map +1 -0
- package/dist/node_modules/boolbase/index.js +19 -0
- package/dist/node_modules/boolbase/index.js.map +1 -0
- package/dist/node_modules/css-select/lib/attributes.js +203 -0
- package/dist/node_modules/css-select/lib/attributes.js.map +1 -0
- package/dist/node_modules/css-select/lib/compile.js +141 -0
- package/dist/node_modules/css-select/lib/compile.js.map +1 -0
- package/dist/node_modules/css-select/lib/general.js +154 -0
- package/dist/node_modules/css-select/lib/general.js.map +1 -0
- package/dist/node_modules/css-select/lib/index.js +128 -0
- package/dist/node_modules/css-select/lib/index.js.map +1 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/aliases.js +40 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/aliases.js.map +1 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/filters.js +163 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/filters.js.map +1 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/index.js +71 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/index.js.map +1 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/pseudos.js +93 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/pseudos.js.map +1 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/subselects.js +111 -0
- package/dist/node_modules/css-select/lib/pseudo-selectors/subselects.js.map +1 -0
- package/dist/node_modules/css-select/lib/sort.js +78 -0
- package/dist/node_modules/css-select/lib/sort.js.map +1 -0
- package/dist/node_modules/css-what/lib/es/index.js +12 -0
- package/dist/node_modules/css-what/lib/es/index.js.map +1 -0
- package/dist/node_modules/css-what/lib/es/parse.js +349 -0
- package/dist/node_modules/css-what/lib/es/parse.js.map +1 -0
- package/dist/node_modules/css-what/lib/es/stringify.js +102 -0
- package/dist/node_modules/css-what/lib/es/stringify.js.map +1 -0
- package/dist/node_modules/css-what/lib/es/types.js +37 -0
- package/dist/node_modules/css-what/lib/es/types.js.map +1 -0
- package/dist/node_modules/dom-serializer/lib/foreignNames.js +117 -0
- package/dist/node_modules/dom-serializer/lib/foreignNames.js.map +1 -0
- package/dist/node_modules/dom-serializer/lib/index.js +207 -0
- package/dist/node_modules/dom-serializer/lib/index.js.map +1 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/decode.js +368 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/decode.js.map +1 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/decode_codepoint.js +70 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/decode_codepoint.js.map +1 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/encode.js +61 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/encode.js.map +1 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/escape.js +79 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/escape.js.map +1 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/decode-data-html.js +18 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/decode-data-html.js.map +1 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/decode-data-xml.js +18 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/decode-data-xml.js.map +1 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/encode-html.js +19 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/generated/encode-html.js.map +1 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/index.js +139 -0
- package/dist/node_modules/dom-serializer/node_modules/entities/lib/index.js.map +1 -0
- package/dist/node_modules/domelementtype/lib/index.js +40 -0
- package/dist/node_modules/domelementtype/lib/index.js.map +1 -0
- package/dist/node_modules/domhandler/lib/index.js +167 -0
- package/dist/node_modules/domhandler/lib/index.js.map +1 -0
- package/dist/node_modules/domhandler/lib/node.js +439 -0
- package/dist/node_modules/domhandler/lib/node.js.map +1 -0
- package/dist/node_modules/domutils/lib/feeds.js +146 -0
- package/dist/node_modules/domutils/lib/feeds.js.map +1 -0
- package/dist/node_modules/domutils/lib/helpers.js +97 -0
- package/dist/node_modules/domutils/lib/helpers.js.map +1 -0
- package/dist/node_modules/domutils/lib/index.js +65 -0
- package/dist/node_modules/domutils/lib/index.js.map +1 -0
- package/dist/node_modules/domutils/lib/legacy.js +124 -0
- package/dist/node_modules/domutils/lib/legacy.js.map +1 -0
- package/dist/node_modules/domutils/lib/manipulation.js +107 -0
- package/dist/node_modules/domutils/lib/manipulation.js.map +1 -0
- package/dist/node_modules/domutils/lib/querying.js +102 -0
- package/dist/node_modules/domutils/lib/querying.js.map +1 -0
- package/dist/node_modules/domutils/lib/stringify.js +65 -0
- package/dist/node_modules/domutils/lib/stringify.js.map +1 -0
- package/dist/node_modules/domutils/lib/traversal.js +69 -0
- package/dist/node_modules/domutils/lib/traversal.js.map +1 -0
- package/dist/node_modules/he/he.js +256 -0
- package/dist/node_modules/he/he.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/back.js +16 -0
- package/dist/node_modules/node-html-parser/dist/back.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/index.js +48 -0
- package/dist/node_modules/node-html-parser/dist/index.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/matcher.js +112 -0
- package/dist/node_modules/node-html-parser/dist/matcher.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/nodes/comment.js +41 -0
- package/dist/node_modules/node-html-parser/dist/nodes/comment.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/nodes/html.js +1048 -0
- package/dist/node_modules/node-html-parser/dist/nodes/html.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/nodes/node.js +49 -0
- package/dist/node_modules/node-html-parser/dist/nodes/node.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/nodes/text.js +106 -0
- package/dist/node_modules/node-html-parser/dist/nodes/text.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/nodes/type.js +19 -0
- package/dist/node_modules/node-html-parser/dist/nodes/type.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/parse.js +20 -0
- package/dist/node_modules/node-html-parser/dist/parse.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/valid.js +19 -0
- package/dist/node_modules/node-html-parser/dist/valid.js.map +1 -0
- package/dist/node_modules/node-html-parser/dist/void-tag.js +36 -0
- package/dist/node_modules/node-html-parser/dist/void-tag.js.map +1 -0
- package/dist/node_modules/nth-check/lib/compile.js +76 -0
- package/dist/node_modules/nth-check/lib/compile.js.map +1 -0
- package/dist/node_modules/nth-check/lib/index.js +36 -0
- package/dist/node_modules/nth-check/lib/index.js.map +1 -0
- package/dist/node_modules/nth-check/lib/parse.js +69 -0
- package/dist/node_modules/nth-check/lib/parse.js.map +1 -0
- package/package.json +2 -2
- package/dist/fulltext/attach-shared.d.ts.map +0 -1
- package/dist/fulltext/attach-shared.js.map +0 -1
- package/dist/fulltext/citation-key.d.ts +0 -15
- package/dist/fulltext/citation-key.d.ts.map +0 -1
- package/dist/fulltext/citation-key.js.map +0 -1
- package/dist/fulltext/convert/index.d.ts +0 -20
- package/dist/fulltext/convert/index.d.ts.map +0 -1
- package/dist/fulltext/convert/index.js +0 -50
- package/dist/fulltext/convert/index.js.map +0 -1
- package/dist/fulltext/convert/jats-parser.d.ts +0 -36
- package/dist/fulltext/convert/jats-parser.d.ts.map +0 -1
- package/dist/fulltext/convert/jats-parser.js +0 -887
- package/dist/fulltext/convert/jats-parser.js.map +0 -1
- package/dist/fulltext/convert/markdown-writer.d.ts +0 -6
- package/dist/fulltext/convert/markdown-writer.d.ts.map +0 -1
- package/dist/fulltext/convert/markdown-writer.js.map +0 -1
- package/dist/fulltext/convert/types.d.ts +0 -141
- package/dist/fulltext/convert/types.d.ts.map +0 -1
- package/dist/fulltext/discovery/arxiv.d.ts +0 -11
- package/dist/fulltext/discovery/arxiv.d.ts.map +0 -1
- package/dist/fulltext/discovery/arxiv.js.map +0 -1
- package/dist/fulltext/discovery/core.d.ts +0 -11
- package/dist/fulltext/discovery/core.d.ts.map +0 -1
- package/dist/fulltext/discovery/core.js.map +0 -1
- package/dist/fulltext/discovery/index.d.ts +0 -28
- package/dist/fulltext/discovery/index.d.ts.map +0 -1
- package/dist/fulltext/discovery/index.js +0 -75
- package/dist/fulltext/discovery/index.js.map +0 -1
- package/dist/fulltext/discovery/pmc.d.ts +0 -19
- package/dist/fulltext/discovery/pmc.d.ts.map +0 -1
- package/dist/fulltext/discovery/pmc.js.map +0 -1
- package/dist/fulltext/discovery/unpaywall.d.ts +0 -11
- package/dist/fulltext/discovery/unpaywall.d.ts.map +0 -1
- package/dist/fulltext/discovery/unpaywall.js.map +0 -1
- package/dist/fulltext/download/downloader.d.ts +0 -21
- package/dist/fulltext/download/downloader.d.ts.map +0 -1
- package/dist/fulltext/download/downloader.js +0 -59
- package/dist/fulltext/download/downloader.js.map +0 -1
- package/dist/fulltext/download/orchestrator.d.ts +0 -33
- package/dist/fulltext/download/orchestrator.d.ts.map +0 -1
- package/dist/fulltext/download/orchestrator.js +0 -125
- package/dist/fulltext/download/orchestrator.js.map +0 -1
- package/dist/fulltext/download/pmc-xml.d.ts +0 -13
- package/dist/fulltext/download/pmc-xml.d.ts.map +0 -1
- package/dist/fulltext/download/pmc-xml.js.map +0 -1
- package/dist/fulltext/meta.d.ts +0 -25
- package/dist/fulltext/meta.d.ts.map +0 -1
- package/dist/fulltext/meta.js.map +0 -1
- package/dist/fulltext/paths.d.ts +0 -12
- package/dist/fulltext/paths.d.ts.map +0 -1
- package/dist/fulltext/paths.js.map +0 -1
- package/dist/fulltext/readme.d.ts +0 -4
- package/dist/fulltext/readme.d.ts.map +0 -1
- package/dist/fulltext/readme.js.map +0 -1
- package/dist/fulltext/types.d.ts +0 -90
- package/dist/fulltext/types.d.ts.map +0 -1
- /package/dist/{fulltext → integration}/attach-shared.js +0 -0
- /package/dist/{fulltext → node_modules/@ncukondo/academic-fulltext/dist}/paths.js +0 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sync.js","sources":["../../../../src/cli/commands/fulltext/sync.ts"],"sourcesContent":["/**\n * fulltext sync command - Detect and register manually added files.\n */\n\nimport { join } from 'node:path';\nimport { readFile, writeFile, readdir, stat } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ReviewFile } from '../review/types.js';\nimport
|
|
1
|
+
{"version":3,"file":"sync.js","sources":["../../../../src/cli/commands/fulltext/sync.ts"],"sourcesContent":["/**\n * fulltext sync command - Detect and register manually added files.\n */\n\nimport { join } from 'node:path';\nimport { readFile, writeFile, readdir, stat } from 'node:fs/promises';\nimport { parse as parseYaml, stringify as stringifyYaml } from 'yaml';\nimport type { ReviewFile } from '../review/types.js';\nimport { loadMeta, saveMeta, updateMetaFiles, getFulltextDir, type FulltextMeta, type FileInfo } from '@ncukondo/academic-fulltext';\n\n/** Known fulltext filenames and their type keys. */\nconst FULLTEXT_FILES: Record<string, 'pdf' | 'xml' | 'html' | 'markdown'> = {\n 'fulltext.pdf': 'pdf',\n 'fulltext.xml': 'xml',\n 'fulltext.html': 'html',\n 'fulltext.md': 'markdown',\n};\n\nexport interface FulltextSyncOptions {\n sessionId: string;\n sessionsDir: string;\n dryRun?: boolean;\n}\n\nexport interface FulltextSyncEntry {\n dirName: string;\n files: string[];\n sizes: number[];\n}\n\nexport interface FulltextSyncResult {\n /** Total number of new files synced */\n synced: number;\n /** Number of articles updated */\n articlesUpdated: number;\n entries: FulltextSyncEntry[];\n dryRun?: boolean;\n}\n\nexport async function executeFulltextSync(\n options: FulltextSyncOptions,\n): Promise<FulltextSyncResult> {\n const { sessionId, sessionsDir, dryRun } = options;\n const sessionDir = join(sessionsDir, sessionId);\n const fulltextDir = getFulltextDir(sessionDir);\n\n // Scan all directories in fulltext/\n let dirEntries: string[];\n try {\n const entries = await readdir(fulltextDir, { withFileTypes: true });\n dirEntries = entries.filter(e => e.isDirectory()).map(e => e.name);\n } catch {\n return { synced: 0, articlesUpdated: 0, entries: [] };\n }\n\n const syncEntries: FulltextSyncEntry[] = [];\n let totalSynced = 0;\n const articlesWithChanges = new Set<string>();\n\n // Track updates for meta and reviews\n const hasFilesUpdates = new Map<string, { pdf: boolean; xml: boolean; html: boolean; markdown: boolean }>();\n const metaUpdates = new Map<string, { meta: FulltextMeta; path: string }>();\n\n for (const dirName of dirEntries) {\n const articleDir = join(fulltextDir, dirName);\n const metaPath = join(articleDir, 'meta.json');\n\n // Load meta.json\n let meta: FulltextMeta;\n try {\n meta = await loadMeta(metaPath);\n } catch {\n // Skip directories without meta.json\n continue;\n }\n\n const newFiles: string[] = [];\n const newSizes: number[] = [];\n const fileInfoUpdates: { pdf?: FileInfo; xml?: FileInfo; html?: FileInfo; markdown?: FileInfo } = {};\n\n for (const [filename, typeKey] of Object.entries(FULLTEXT_FILES)) {\n // Skip if already tracked in meta\n if (meta.files[typeKey]) {\n continue;\n }\n\n // Check if file exists\n const filePath = join(articleDir, filename);\n let fileStat;\n try {\n fileStat = await stat(filePath);\n } catch {\n continue; // File doesn't exist\n }\n\n if (!fileStat.isFile()) continue;\n\n // New file found\n const fileInfo: FileInfo = {\n filename,\n source: 'manual',\n retrievedAt: new Date().toISOString(),\n size: fileStat.size,\n };\n\n fileInfoUpdates[typeKey] = fileInfo;\n newFiles.push(filename);\n newSizes.push(fileStat.size);\n }\n\n if (newFiles.length > 0) {\n totalSynced += newFiles.length;\n articlesWithChanges.add(dirName);\n\n syncEntries.push({\n dirName,\n files: newFiles,\n sizes: newSizes,\n });\n\n if (!dryRun) {\n // Update meta.json\n const updatedMeta = updateMetaFiles(meta, fileInfoUpdates);\n metaUpdates.set(dirName, { meta: updatedMeta, path: metaPath });\n\n // Compute updated hasFiles for reviews\n const hasFiles = {\n pdf: !!(updatedMeta.files.pdf),\n xml: !!(updatedMeta.files.xml),\n html: !!(updatedMeta.files.html),\n markdown: !!(updatedMeta.files.markdown),\n };\n hasFilesUpdates.set(dirName, hasFiles);\n }\n }\n }\n\n if (!dryRun && metaUpdates.size > 0) {\n // Save all meta.json updates\n for (const [, { meta, path }] of metaUpdates) {\n await saveMeta(path, meta);\n }\n\n // Update reviews.yaml if it exists\n const reviewsPath = join(sessionDir, '.internal', 'reviews.yaml');\n try {\n const reviewContent = await readFile(reviewsPath, 'utf-8');\n const reviewFile = parseYaml(reviewContent) as ReviewFile;\n let reviewsChanged = false;\n\n for (const article of reviewFile.articles) {\n if (article.fulltext?.dirName && hasFilesUpdates.has(article.fulltext.dirName)) {\n const hasFiles = hasFilesUpdates.get(article.fulltext.dirName);\n if (hasFiles) {\n article.fulltext.hasFiles = hasFiles;\n reviewsChanged = true;\n }\n }\n }\n\n if (reviewsChanged) {\n await writeFile(reviewsPath, stringifyYaml(reviewFile), 'utf-8');\n }\n } catch {\n // No reviews.yaml - that's fine\n }\n }\n\n return {\n synced: totalSynced,\n articlesUpdated: articlesWithChanges.size,\n entries: syncEntries,\n ...(dryRun && { dryRun: true }),\n };\n}\n"],"names":["parseYaml","stringifyYaml"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/types.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/review/types.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","sources":["../../../../src/cli/commands/review/types.ts"],"sourcesContent":["/**\n * Review workflow types for article assessment tracking\n */\n\nimport type { ArticleFulltextRef } from '../../../fulltext/types.js';\n\nexport type ReviewDecision = 'include' | 'exclude' | 'uncertain';\n\n/**\n * Basis of the review decision (what information was used)\n */\nexport type ReviewBasis = 'title' | 'abstract' | 'fulltext';\n\n/**\n * Individual assessment of an article by a reviewer\n */\nexport interface Review {\n /** Reviewer identifier: \"human:name\" or \"ai:name\" */\n reviewer: string;\n /** Assessment decision */\n decision?: ReviewDecision;\n /** Basis of the decision (what information was used) */\n basis?: ReviewBasis;\n /** Optional comment or reason */\n comment?: string;\n /** ISO 8601 timestamp (optional - auto-assigned on merge if not provided) */\n timestamp?: string;\n}\n\n/**\n * Source information for merged duplicates\n */\nexport interface MergedSource {\n source: string;\n pmid?: string;\n doi?: string;\n scopusId?: string;\n arxivId?: string;\n ericId?: string;\n}\n\n/**\n * Article entry with identifiers, bibliographic info, and reviews\n */\nexport interface ArticleEntry {\n // Identifiers (at least one required for matching)\n doi?: string;\n pmid?: string;\n scopusId?: string;\n arxivId?: string;\n ericId?: string;\n\n // Bibliographic info (for reviewer reference)\n title: string;\n authors?: string;\n year?: string;\n abstract?: string;\n\n // Deduplication tracking\n mergedFrom?: MergedSource[];\n\n // Review data\n reviews: Review[];\n /** Historical reviews (only in extracted ReviewFiles, never in master file) */\n reviewHistory?: Review[];\n finalDecision?: 'include' | 'exclude' | null;\n\n // Fulltext reference (set by fulltext init/sync)\n fulltext?: ArticleFulltextRef;\n}\n\n/**\n * Top-level structure of the reviews.yaml file\n */\nexport interface ReviewerRecord {\n name: string;\n basis: ReviewBasis;\n}\n\nexport interface ReviewFile {\n sessionId: string;\n /** Path to inclusion criteria file */\n criteria?: string;\n /** Reviewer identifier (only in extracted ReviewFiles) */\n reviewer?: string;\n /** Basis level for screening (only in extracted ReviewFiles) */\n basis?: ReviewBasis;\n articles: ArticleEntry[];\n /** Registry of reviewers who participated at each basis level */\n reviewers?: ReviewerRecord[];\n}\n\n/**\n * Work file article entry for AI agent workflow\n */\n/** @deprecated Use ReviewFile format with reviews[] instead. Kept for backward compatibility. */\nexport interface WorkFileArticle {\n id: string;\n title: string;\n abstract?: string;\n /** Fulltext directory name (only for fulltext basis) */\n fulltext?: string;\n decision: ReviewDecision | null;\n comment: string;\n}\n\n/**\n * Work file structure for AI agent workflow\n */\n/** @deprecated Use ReviewFile format with basis field instead. Kept for backward compatibility. */\nexport interface WorkFile {\n sessionId: string;\n basis: ReviewBasis;\n reviewer: string;\n articles: WorkFileArticle[];\n}\n\n/**\n * Basis priority rank: fulltext > abstract > title > undefined\n */\nconst BASIS_RANK: Record<string, number> = {\n title: 1,\n abstract: 2,\n fulltext: 3,\n};\n\nexport function basisRank(basis: ReviewBasis | undefined): number {\n if (basis === undefined) return 0;\n return BASIS_RANK[basis] ?? 0;\n}\n\n/**\n * Review status classification (7-state model)\n */\nexport type ReviewStatus =\n | 'pending'\n | 'incomplete'\n | 'uncertain'\n | 'agreed-include'\n | 'agreed-exclude'\n | 'conflicting'\n | 'finalized';\n\n/**\n * Classify the review status of an article entry\n *\n * Classification logic (in order):\n * 1. finalDecision set? → finalized\n * 2. No reviews? → pending\n * 3. Registered reviewer missing? → incomplete\n * 4. include AND exclude present? → conflicting\n * 5. Any uncertain? → uncertain\n * 6. All include? → agreed-include\n * 7. All exclude? → agreed-exclude\n */\nexport function classifyStatus(\n entry: ArticleEntry,\n registeredReviewers?: ReviewerRecord[]\n): ReviewStatus {\n // 1. Finalized takes precedence\n if (entry.finalDecision !== undefined && entry.finalDecision !== null) {\n return 'finalized';\n }\n\n // No reviews = pending (reviews can be null from YAML parsing with only comments)\n const reviews = entry.reviews ?? [];\n if (reviews.length === 0) {\n return 'pending';\n }\n\n // 3. Check for incomplete (registered reviewer missing)\n // Only check reviewers whose registered basis ≤ article's highest reviewed basis\n if (registeredReviewers && registeredReviewers.length > 0) {\n const reviewerNames = new Set(reviews.map((r) => r.reviewer));\n let highestReviewedRank = 0;\n for (const r of reviews) {\n highestReviewedRank = Math.max(highestReviewedRank, basisRank(r.basis));\n }\n // When reviews have no basis (legacy), check all registered reviewers\n const applicableReviewers = highestReviewedRank === 0\n ? registeredReviewers\n : registeredReviewers.filter(\n (reg) => basisRank(reg.basis) <= highestReviewedRank\n );\n const hasAllReviewers = applicableReviewers.every((reg) =>\n reviewerNames.has(reg.name)\n );\n if (applicableReviewers.length > 0 && !hasAllReviewers) {\n return 'incomplete';\n }\n }\n\n // Get reviews that have decisions\n const reviewsWithDecisions = reviews.filter((r) => r.decision !== undefined);\n\n if (reviewsWithDecisions.length === 0) {\n // All reviews lack a decision — treat as pending\n return 'pending';\n }\n\n // Basis-priority resolution:\n // \"uncertain\" at a lower basis means \"need more info\" (escalate).\n // A definitive decision at a higher basis resolves that uncertainty.\n //\n // Algorithm:\n // 1. Find the highest basis rank among all definitive (include/exclude) reviews\n // 2. For each reviewer, compute their effective decision:\n // - Take their highest-basis definitive decision if they have one\n // - Otherwise, keep uncertain only if their uncertain rank >= highest definitive rank\n // (i.e., no higher-basis definitive exists globally to resolve it)\n // 3. Reviewers whose only reviews are uncertain at a lower basis than the\n // highest global definitive are excluded from consensus (their uncertainty was resolved)\n\n // Find highest definitive basis rank across ALL reviews\n let highestDefinitiveRank = 0;\n for (const r of reviewsWithDecisions) {\n if (r.decision !== 'uncertain') {\n highestDefinitiveRank = Math.max(highestDefinitiveRank, basisRank(r.basis));\n }\n }\n\n // For each reviewer, compute effective decision\n const reviewerMap = new Map<string, { decision: ReviewDecision; rank: number }>();\n for (const r of reviewsWithDecisions) {\n const rank = basisRank(r.basis);\n const existing = reviewerMap.get(r.reviewer);\n if (!existing) {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else {\n // Prefer definitive over uncertain\n if (r.decision !== 'uncertain' && existing.decision === 'uncertain') {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else if (r.decision !== 'uncertain' && existing.decision !== 'uncertain' && rank > existing.rank) {\n // Higher-basis definitive overrides lower-basis definitive\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else if (r.decision === 'uncertain' && existing.decision === 'uncertain' && rank > existing.rank) {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n }\n }\n }\n\n // Collect effective decisions, excluding reviewers whose effective decision\n // is at a lower basis than the highest global definitive\n const effectiveDecisions: ReviewDecision[] = [];\n for (const { decision, rank } of reviewerMap.values()) {\n if (rank < highestDefinitiveRank) {\n // This reviewer's decision is at a lower basis than the highest definitive — skip\n continue;\n }\n effectiveDecisions.push(decision);\n }\n\n if (effectiveDecisions.length === 0) {\n return 'pending';\n }\n\n // 4. Check for conflicts: both include and exclude present among effective decisions\n const hasInclude = effectiveDecisions.includes('include');\n const hasExclude = effectiveDecisions.includes('exclude');\n if (hasInclude && hasExclude) {\n return 'conflicting';\n }\n\n // 5. Any effective uncertain?\n const hasUncertain = effectiveDecisions.includes('uncertain');\n if (hasUncertain) {\n return 'uncertain';\n }\n\n // 6. All include?\n if (effectiveDecisions.every((d) => d === 'include')) {\n return 'agreed-include';\n }\n\n // 7. All exclude (only remaining possibility after ruling out conflicts and uncertain)\n return 'agreed-exclude';\n}\n"],"names":[],"mappings":"AAwHA,MAAM,aAAqC;AAAA,EACzC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,UAAU;AACZ;AAEO,SAAS,UAAU,OAAwC;AAChE,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,WAAW,KAAK,KAAK;AAC9B;AA0BO,SAAS,eACd,OACA,qBACc;AAEd,MAAI,MAAM,kBAAkB,UAAa,MAAM,kBAAkB,MAAM;AACrE,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,MAAM,WAAW,CAAA;AACjC,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAIA,MAAI,uBAAuB,oBAAoB,SAAS,GAAG;AACzD,UAAM,gBAAgB,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAC5D,QAAI,sBAAsB;AAC1B,eAAW,KAAK,SAAS;AACvB,4BAAsB,KAAK,IAAI,qBAAqB,UAAU,EAAE,KAAK,CAAC;AAAA,IACxE;AAEA,UAAM,sBAAsB,wBAAwB,IAChD,sBACA,oBAAoB;AAAA,MAClB,CAAC,QAAQ,UAAU,IAAI,KAAK,KAAK;AAAA,IAAA;AAEvC,UAAM,kBAAkB,oBAAoB;AAAA,MAAM,CAAC,QACjD,cAAc,IAAI,IAAI,IAAI;AAAA,IAAA;AAE5B,QAAI,oBAAoB,SAAS,KAAK,CAAC,iBAAiB;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,uBAAuB,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,MAAS;AAE3E,MAAI,qBAAqB,WAAW,GAAG;AAErC,WAAO;AAAA,EACT;AAgBA,MAAI,wBAAwB;AAC5B,aAAW,KAAK,sBAAsB;AACpC,QAAI,EAAE,aAAa,aAAa;AAC9B,8BAAwB,KAAK,IAAI,uBAAuB,UAAU,EAAE,KAAK,CAAC;AAAA,IAC5E;AAAA,EACF;AAGA,QAAM,kCAAkB,IAAA;AACxB,aAAW,KAAK,sBAAsB;AACpC,UAAM,OAAO,UAAU,EAAE,KAAK;AAC9B,UAAM,WAAW,YAAY,IAAI,EAAE,QAAQ;AAC3C,QAAI,CAAC,UAAU;AACb,kBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,IAC7D,OAAO;AAEL,UAAI,EAAE,aAAa,eAAe,SAAS,aAAa,aAAa;AACnE,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D,WAAW,EAAE,aAAa,eAAe,SAAS,aAAa,eAAe,OAAO,SAAS,MAAM;AAElG,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D,WAAW,EAAE,aAAa,eAAe,SAAS,aAAa,eAAe,OAAO,SAAS,MAAM;AAClG,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAIA,QAAM,qBAAuC,CAAA;AAC7C,aAAW,EAAE,UAAU,KAAA,KAAU,YAAY,UAAU;AACrD,QAAI,OAAO,uBAAuB;AAEhC;AAAA,IACF;AACA,uBAAmB,KAAK,QAAQ;AAAA,EAClC;AAEA,MAAI,mBAAmB,WAAW,GAAG;AACnC,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,mBAAmB,SAAS,SAAS;AACxD,QAAM,aAAa,mBAAmB,SAAS,SAAS;AACxD,MAAI,cAAc,YAAY;AAC5B,WAAO;AAAA,EACT;AAGA,QAAM,eAAe,mBAAmB,SAAS,WAAW;AAC5D,MAAI,cAAc;AAChB,WAAO;AAAA,EACT;AAGA,MAAI,mBAAmB,MAAM,CAAC,MAAM,MAAM,SAAS,GAAG;AACpD,WAAO;AAAA,EACT;AAGA,SAAO;AACT;"}
|
|
1
|
+
{"version":3,"file":"types.js","sources":["../../../../src/cli/commands/review/types.ts"],"sourcesContent":["/**\n * Review workflow types for article assessment tracking\n */\n\nimport type { ArticleFulltextRef } from '@ncukondo/academic-fulltext';\n\nexport type ReviewDecision = 'include' | 'exclude' | 'uncertain';\n\n/**\n * Basis of the review decision (what information was used)\n */\nexport type ReviewBasis = 'title' | 'abstract' | 'fulltext';\n\n/**\n * Individual assessment of an article by a reviewer\n */\nexport interface Review {\n /** Reviewer identifier: \"human:name\" or \"ai:name\" */\n reviewer: string;\n /** Assessment decision */\n decision?: ReviewDecision;\n /** Basis of the decision (what information was used) */\n basis?: ReviewBasis;\n /** Optional comment or reason */\n comment?: string;\n /** ISO 8601 timestamp (optional - auto-assigned on merge if not provided) */\n timestamp?: string;\n}\n\n/**\n * Source information for merged duplicates\n */\nexport interface MergedSource {\n source: string;\n pmid?: string;\n doi?: string;\n scopusId?: string;\n arxivId?: string;\n ericId?: string;\n}\n\n/**\n * Article entry with identifiers, bibliographic info, and reviews\n */\nexport interface ArticleEntry {\n // Identifiers (at least one required for matching)\n doi?: string;\n pmid?: string;\n scopusId?: string;\n arxivId?: string;\n ericId?: string;\n\n // Bibliographic info (for reviewer reference)\n title: string;\n authors?: string;\n year?: string;\n abstract?: string;\n\n // Deduplication tracking\n mergedFrom?: MergedSource[];\n\n // Review data\n reviews: Review[];\n /** Historical reviews (only in extracted ReviewFiles, never in master file) */\n reviewHistory?: Review[];\n finalDecision?: 'include' | 'exclude' | null;\n\n // Fulltext reference (set by fulltext init/sync)\n fulltext?: ArticleFulltextRef;\n}\n\n/**\n * Top-level structure of the reviews.yaml file\n */\nexport interface ReviewerRecord {\n name: string;\n basis: ReviewBasis;\n}\n\nexport interface ReviewFile {\n sessionId: string;\n /** Path to inclusion criteria file */\n criteria?: string;\n /** Reviewer identifier (only in extracted ReviewFiles) */\n reviewer?: string;\n /** Basis level for screening (only in extracted ReviewFiles) */\n basis?: ReviewBasis;\n articles: ArticleEntry[];\n /** Registry of reviewers who participated at each basis level */\n reviewers?: ReviewerRecord[];\n}\n\n/**\n * Work file article entry for AI agent workflow\n */\n/** @deprecated Use ReviewFile format with reviews[] instead. Kept for backward compatibility. */\nexport interface WorkFileArticle {\n id: string;\n title: string;\n abstract?: string;\n /** Fulltext directory name (only for fulltext basis) */\n fulltext?: string;\n decision: ReviewDecision | null;\n comment: string;\n}\n\n/**\n * Work file structure for AI agent workflow\n */\n/** @deprecated Use ReviewFile format with basis field instead. Kept for backward compatibility. */\nexport interface WorkFile {\n sessionId: string;\n basis: ReviewBasis;\n reviewer: string;\n articles: WorkFileArticle[];\n}\n\n/**\n * Basis priority rank: fulltext > abstract > title > undefined\n */\nconst BASIS_RANK: Record<string, number> = {\n title: 1,\n abstract: 2,\n fulltext: 3,\n};\n\nexport function basisRank(basis: ReviewBasis | undefined): number {\n if (basis === undefined) return 0;\n return BASIS_RANK[basis] ?? 0;\n}\n\n/**\n * Review status classification (7-state model)\n */\nexport type ReviewStatus =\n | 'pending'\n | 'incomplete'\n | 'uncertain'\n | 'agreed-include'\n | 'agreed-exclude'\n | 'conflicting'\n | 'finalized';\n\n/**\n * Classify the review status of an article entry\n *\n * Classification logic (in order):\n * 1. finalDecision set? → finalized\n * 2. No reviews? → pending\n * 3. Registered reviewer missing? → incomplete\n * 4. include AND exclude present? → conflicting\n * 5. Any uncertain? → uncertain\n * 6. All include? → agreed-include\n * 7. All exclude? → agreed-exclude\n */\nexport function classifyStatus(\n entry: ArticleEntry,\n registeredReviewers?: ReviewerRecord[]\n): ReviewStatus {\n // 1. Finalized takes precedence\n if (entry.finalDecision !== undefined && entry.finalDecision !== null) {\n return 'finalized';\n }\n\n // No reviews = pending (reviews can be null from YAML parsing with only comments)\n const reviews = entry.reviews ?? [];\n if (reviews.length === 0) {\n return 'pending';\n }\n\n // 3. Check for incomplete (registered reviewer missing)\n // Only check reviewers whose registered basis ≤ article's highest reviewed basis\n if (registeredReviewers && registeredReviewers.length > 0) {\n const reviewerNames = new Set(reviews.map((r) => r.reviewer));\n let highestReviewedRank = 0;\n for (const r of reviews) {\n highestReviewedRank = Math.max(highestReviewedRank, basisRank(r.basis));\n }\n // When reviews have no basis (legacy), check all registered reviewers\n const applicableReviewers = highestReviewedRank === 0\n ? registeredReviewers\n : registeredReviewers.filter(\n (reg) => basisRank(reg.basis) <= highestReviewedRank\n );\n const hasAllReviewers = applicableReviewers.every((reg) =>\n reviewerNames.has(reg.name)\n );\n if (applicableReviewers.length > 0 && !hasAllReviewers) {\n return 'incomplete';\n }\n }\n\n // Get reviews that have decisions\n const reviewsWithDecisions = reviews.filter((r) => r.decision !== undefined);\n\n if (reviewsWithDecisions.length === 0) {\n // All reviews lack a decision — treat as pending\n return 'pending';\n }\n\n // Basis-priority resolution:\n // \"uncertain\" at a lower basis means \"need more info\" (escalate).\n // A definitive decision at a higher basis resolves that uncertainty.\n //\n // Algorithm:\n // 1. Find the highest basis rank among all definitive (include/exclude) reviews\n // 2. For each reviewer, compute their effective decision:\n // - Take their highest-basis definitive decision if they have one\n // - Otherwise, keep uncertain only if their uncertain rank >= highest definitive rank\n // (i.e., no higher-basis definitive exists globally to resolve it)\n // 3. Reviewers whose only reviews are uncertain at a lower basis than the\n // highest global definitive are excluded from consensus (their uncertainty was resolved)\n\n // Find highest definitive basis rank across ALL reviews\n let highestDefinitiveRank = 0;\n for (const r of reviewsWithDecisions) {\n if (r.decision !== 'uncertain') {\n highestDefinitiveRank = Math.max(highestDefinitiveRank, basisRank(r.basis));\n }\n }\n\n // For each reviewer, compute effective decision\n const reviewerMap = new Map<string, { decision: ReviewDecision; rank: number }>();\n for (const r of reviewsWithDecisions) {\n const rank = basisRank(r.basis);\n const existing = reviewerMap.get(r.reviewer);\n if (!existing) {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else {\n // Prefer definitive over uncertain\n if (r.decision !== 'uncertain' && existing.decision === 'uncertain') {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else if (r.decision !== 'uncertain' && existing.decision !== 'uncertain' && rank > existing.rank) {\n // Higher-basis definitive overrides lower-basis definitive\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n } else if (r.decision === 'uncertain' && existing.decision === 'uncertain' && rank > existing.rank) {\n reviewerMap.set(r.reviewer, { decision: r.decision!, rank });\n }\n }\n }\n\n // Collect effective decisions, excluding reviewers whose effective decision\n // is at a lower basis than the highest global definitive\n const effectiveDecisions: ReviewDecision[] = [];\n for (const { decision, rank } of reviewerMap.values()) {\n if (rank < highestDefinitiveRank) {\n // This reviewer's decision is at a lower basis than the highest definitive — skip\n continue;\n }\n effectiveDecisions.push(decision);\n }\n\n if (effectiveDecisions.length === 0) {\n return 'pending';\n }\n\n // 4. Check for conflicts: both include and exclude present among effective decisions\n const hasInclude = effectiveDecisions.includes('include');\n const hasExclude = effectiveDecisions.includes('exclude');\n if (hasInclude && hasExclude) {\n return 'conflicting';\n }\n\n // 5. Any effective uncertain?\n const hasUncertain = effectiveDecisions.includes('uncertain');\n if (hasUncertain) {\n return 'uncertain';\n }\n\n // 6. All include?\n if (effectiveDecisions.every((d) => d === 'include')) {\n return 'agreed-include';\n }\n\n // 7. All exclude (only remaining possibility after ruling out conflicts and uncertain)\n return 'agreed-exclude';\n}\n"],"names":[],"mappings":"AAwHA,MAAM,aAAqC;AAAA,EACzC,OAAO;AAAA,EACP,UAAU;AAAA,EACV,UAAU;AACZ;AAEO,SAAS,UAAU,OAAwC;AAChE,MAAI,UAAU,OAAW,QAAO;AAChC,SAAO,WAAW,KAAK,KAAK;AAC9B;AA0BO,SAAS,eACd,OACA,qBACc;AAEd,MAAI,MAAM,kBAAkB,UAAa,MAAM,kBAAkB,MAAM;AACrE,WAAO;AAAA,EACT;AAGA,QAAM,UAAU,MAAM,WAAW,CAAA;AACjC,MAAI,QAAQ,WAAW,GAAG;AACxB,WAAO;AAAA,EACT;AAIA,MAAI,uBAAuB,oBAAoB,SAAS,GAAG;AACzD,UAAM,gBAAgB,IAAI,IAAI,QAAQ,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC;AAC5D,QAAI,sBAAsB;AAC1B,eAAW,KAAK,SAAS;AACvB,4BAAsB,KAAK,IAAI,qBAAqB,UAAU,EAAE,KAAK,CAAC;AAAA,IACxE;AAEA,UAAM,sBAAsB,wBAAwB,IAChD,sBACA,oBAAoB;AAAA,MAClB,CAAC,QAAQ,UAAU,IAAI,KAAK,KAAK;AAAA,IAAA;AAEvC,UAAM,kBAAkB,oBAAoB;AAAA,MAAM,CAAC,QACjD,cAAc,IAAI,IAAI,IAAI;AAAA,IAAA;AAE5B,QAAI,oBAAoB,SAAS,KAAK,CAAC,iBAAiB;AACtD,aAAO;AAAA,IACT;AAAA,EACF;AAGA,QAAM,uBAAuB,QAAQ,OAAO,CAAC,MAAM,EAAE,aAAa,MAAS;AAE3E,MAAI,qBAAqB,WAAW,GAAG;AAErC,WAAO;AAAA,EACT;AAgBA,MAAI,wBAAwB;AAC5B,aAAW,KAAK,sBAAsB;AACpC,QAAI,EAAE,aAAa,aAAa;AAC9B,8BAAwB,KAAK,IAAI,uBAAuB,UAAU,EAAE,KAAK,CAAC;AAAA,IAC5E;AAAA,EACF;AAGA,QAAM,kCAAkB,IAAA;AACxB,aAAW,KAAK,sBAAsB;AACpC,UAAM,OAAO,UAAU,EAAE,KAAK;AAC9B,UAAM,WAAW,YAAY,IAAI,EAAE,QAAQ;AAC3C,QAAI,CAAC,UAAU;AACb,kBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,IAC7D,OAAO;AAEL,UAAI,EAAE,aAAa,eAAe,SAAS,aAAa,aAAa;AACnE,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D,WAAW,EAAE,aAAa,eAAe,SAAS,aAAa,eAAe,OAAO,SAAS,MAAM;AAElG,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D,WAAW,EAAE,aAAa,eAAe,SAAS,aAAa,eAAe,OAAO,SAAS,MAAM;AAClG,oBAAY,IAAI,EAAE,UAAU,EAAE,UAAU,EAAE,UAAW,MAAM;AAAA,MAC7D;AAAA,IACF;AAAA,EACF;AAIA,QAAM,qBAAuC,CAAA;AAC7C,aAAW,EAAE,UAAU,KAAA,KAAU,YAAY,UAAU;AACrD,QAAI,OAAO,uBAAuB;AAEhC;AAAA,IACF;AACA,uBAAmB,KAAK,QAAQ;AAAA,EAClC;AAEA,MAAI,mBAAmB,WAAW,GAAG;AACnC,WAAO;AAAA,EACT;AAGA,QAAM,aAAa,mBAAmB,SAAS,SAAS;AACxD,QAAM,aAAa,mBAAmB,SAAS,SAAS;AACxD,MAAI,cAAc,YAAY;AAC5B,WAAO;AAAA,EACT;AAGA,QAAM,eAAe,mBAAmB,SAAS,WAAW;AAC5D,MAAI,cAAc;AAChB,WAAO;AAAA,EACT;AAGA,MAAI,mBAAmB,MAAM,CAAC,MAAM,MAAM,SAAS,GAAG;AACpD,WAAO;AAAA,EACT;AAGA,SAAO;AACT;"}
|
package/dist/config/schema.d.ts
CHANGED
|
@@ -103,6 +103,8 @@ export declare const ConfigSchema: z.ZodObject<{
|
|
|
103
103
|
sources: z.ZodDefault<z.ZodObject<{
|
|
104
104
|
unpaywall_email: z.ZodDefault<z.ZodString>;
|
|
105
105
|
core_api_key: z.ZodDefault<z.ZodString>;
|
|
106
|
+
ncbi_email: z.ZodDefault<z.ZodString>;
|
|
107
|
+
ncbi_tool: z.ZodDefault<z.ZodString>;
|
|
106
108
|
prefer_sources: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
107
109
|
}, z.core.$strip>>;
|
|
108
110
|
download: z.ZodDefault<z.ZodObject<{
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;iBAc/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AASlE;;;GAGG;AACH,eAAO,MAAM,YAAY
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../../src/config/schema.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAaxB;;;GAGG;AACH,eAAO,MAAM,oBAAoB;;;;;;;;;iBAc/B,CAAC;AAEH,MAAM,MAAM,cAAc,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AASlE;;;GAGG;AACH,eAAO,MAAM,YAAY;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;iBA8GvB,CAAC;AAEH,MAAM,MAAM,MAAM,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,YAAY,CAAC,CAAC"}
|
package/dist/config/schema.js
CHANGED
|
@@ -55,10 +55,14 @@ const ConfigSchema = z.object({
|
|
|
55
55
|
sources: z.object({
|
|
56
56
|
unpaywall_email: z.string().default(""),
|
|
57
57
|
core_api_key: z.string().default(""),
|
|
58
|
+
ncbi_email: z.string().default(""),
|
|
59
|
+
ncbi_tool: z.string().default("search-hub"),
|
|
58
60
|
prefer_sources: z.array(z.string()).default(["pmc", "arxiv", "unpaywall", "core"])
|
|
59
61
|
}).default(() => ({
|
|
60
62
|
unpaywall_email: "",
|
|
61
63
|
core_api_key: "",
|
|
64
|
+
ncbi_email: "",
|
|
65
|
+
ncbi_tool: "search-hub",
|
|
62
66
|
prefer_sources: ["pmc", "arxiv", "unpaywall", "core"]
|
|
63
67
|
})),
|
|
64
68
|
download: z.object({
|
|
@@ -75,6 +79,8 @@ const ConfigSchema = z.object({
|
|
|
75
79
|
sources: {
|
|
76
80
|
unpaywall_email: "",
|
|
77
81
|
core_api_key: "",
|
|
82
|
+
ncbi_email: "",
|
|
83
|
+
ncbi_tool: "search-hub",
|
|
78
84
|
prefer_sources: ["pmc", "arxiv", "unpaywall", "core"]
|
|
79
85
|
},
|
|
80
86
|
download: {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sources":["../../src/config/schema.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Default values for provider configuration.\n */\nconst PROVIDER_DEFAULTS = {\n enabled: true,\n rate_limit: 3,\n timeout: 30000,\n retries: 3,\n max_results: 10000,\n} as const;\n\n/**\n * Schema for individual provider configuration.\n * Each provider (PubMed, Scopus, etc.) has similar config options.\n */\nexport const ProviderConfigSchema = z.object({\n enabled: z.boolean().default(PROVIDER_DEFAULTS.enabled),\n api_key: z.string().default(''),\n email: z\n .string()\n .refine((val) => val === '' || /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(val), {\n message: 'Invalid email',\n })\n .default(''),\n rate_limit: z.number().positive().default(PROVIDER_DEFAULTS.rate_limit),\n timeout: z.number().positive().default(PROVIDER_DEFAULTS.timeout),\n retries: z.number().int().min(0).default(PROVIDER_DEFAULTS.retries),\n max_results: z.number().int().positive().default(PROVIDER_DEFAULTS.max_results),\n inst_token: z.string().default(''),\n});\n\nexport type ProviderConfig = z.infer<typeof ProviderConfigSchema>;\n\n/**\n * Get default provider config object.\n */\nfunction getDefaultProviderConfig(): ProviderConfig {\n return ProviderConfigSchema.parse({});\n}\n\n/**\n * Main configuration schema for search-hub.\n * All fields have defaults, so an empty object is valid input.\n */\nexport const ConfigSchema = z.object({\n session: z\n .object({\n // Empty string means use platform default (resolved in loader.ts)\n directory: z.string().default(''),\n })\n .default(() => ({ directory: '' })),\n\n log: z\n .object({\n level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),\n })\n .default(() => ({ level: 'info' as const })),\n\n output: z\n .object({\n color: z.boolean().default(true),\n progress_bar: z.boolean().default(true),\n })\n .default(() => ({ color: true, progress_bar: true })),\n\n providers: z\n .object({\n pubmed: ProviderConfigSchema.default(getDefaultProviderConfig),\n eric: ProviderConfigSchema.default(getDefaultProviderConfig),\n arxiv: ProviderConfigSchema.default(getDefaultProviderConfig),\n scopus: ProviderConfigSchema.default(getDefaultProviderConfig),\n wos: ProviderConfigSchema.default(getDefaultProviderConfig),\n embase: ProviderConfigSchema.default(getDefaultProviderConfig),\n })\n .default(() => ({\n pubmed: getDefaultProviderConfig(),\n eric: getDefaultProviderConfig(),\n arxiv: getDefaultProviderConfig(),\n scopus: getDefaultProviderConfig(),\n wos: getDefaultProviderConfig(),\n embase: getDefaultProviderConfig(),\n })),\n\n fulltext: z\n .object({\n enabled: z.boolean().default(true),\n auto_convert_markdown: z.boolean().default(true),\n auto_attach_on_register: z.boolean().default(true),\n sources: z\n .object({\n unpaywall_email: z.string().default(''),\n core_api_key: z.string().default(''),\n prefer_sources: z.array(z.string()).default(['pmc', 'arxiv', 'unpaywall', 'core']),\n })\n .default(() => ({\n unpaywall_email: '',\n core_api_key: '',\n prefer_sources: ['pmc', 'arxiv', 'unpaywall', 'core'],\n })),\n download: z\n .object({\n concurrent_downloads: z.number().int().positive().default(3),\n retry_attempts: z.number().int().min(0).default(3),\n })\n .default(() => ({\n concurrent_downloads: 3,\n retry_attempts: 3,\n })),\n })\n .default(() => ({\n enabled: true,\n auto_convert_markdown: true,\n auto_attach_on_register: true,\n sources: {\n unpaywall_email: '',\n core_api_key: '',\n prefer_sources: ['pmc', 'arxiv', 'unpaywall', 'core'],\n },\n download: {\n concurrent_downloads: 3,\n retry_attempts: 3,\n },\n })),\n\n integration: z\n .object({\n reference_manager: z\n .object({\n enabled: z.boolean().default(true),\n command: z.string().default('ref'),\n auto_register: z.boolean().default(false),\n with_abstracts: z.boolean().default(false),\n })\n .default(() => ({\n enabled: true,\n command: 'ref',\n auto_register: false,\n with_abstracts: false,\n })),\n })\n .default(() => ({\n reference_manager: {\n enabled: true,\n command: 'ref',\n auto_register: false,\n with_abstracts: false,\n },\n })),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n"],"names":[],"mappings":";AAKA,MAAM,oBAAoB;AAAA,EACxB,SAAS;AAAA,EACT,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,SAAS;AAAA,EACT,aAAa;AACf;AAMO,MAAM,uBAAuB,EAAE,OAAO;AAAA,EAC3C,SAAS,EAAE,QAAA,EAAU,QAAQ,kBAAkB,OAAO;AAAA,EACtD,SAAS,EAAE,SAAS,QAAQ,EAAE;AAAA,EAC9B,OAAO,EACJ,OAAA,EACA,OAAO,CAAC,QAAQ,QAAQ,MAAM,6BAA6B,KAAK,GAAG,GAAG;AAAA,IACrE,SAAS;AAAA,EAAA,CACV,EACA,QAAQ,EAAE;AAAA,EACb,YAAY,EAAE,OAAA,EAAS,WAAW,QAAQ,kBAAkB,UAAU;AAAA,EACtE,SAAS,EAAE,OAAA,EAAS,WAAW,QAAQ,kBAAkB,OAAO;AAAA,EAChE,SAAS,EAAE,OAAA,EAAS,IAAA,EAAM,IAAI,CAAC,EAAE,QAAQ,kBAAkB,OAAO;AAAA,EAClE,aAAa,EAAE,OAAA,EAAS,IAAA,EAAM,WAAW,QAAQ,kBAAkB,WAAW;AAAA,EAC9E,YAAY,EAAE,OAAA,EAAS,QAAQ,EAAE;AACnC,CAAC;AAOD,SAAS,2BAA2C;AAClD,SAAO,qBAAqB,MAAM,EAAE;AACtC;AAMO,MAAM,eAAe,EAAE,OAAO;AAAA,EACnC,SAAS,EACN,OAAO;AAAA;AAAA,IAEN,WAAW,EAAE,OAAA,EAAS,QAAQ,EAAE;AAAA,EAAA,CACjC,EACA,QAAQ,OAAO,EAAE,WAAW,KAAK;AAAA,EAEpC,KAAK,EACF,OAAO;AAAA,IACN,OAAO,EAAE,KAAK,CAAC,SAAS,QAAQ,QAAQ,OAAO,CAAC,EAAE,QAAQ,MAAM;AAAA,EAAA,CACjE,EACA,QAAQ,OAAO,EAAE,OAAO,SAAkB;AAAA,EAE7C,QAAQ,EACL,OAAO;AAAA,IACN,OAAO,EAAE,UAAU,QAAQ,IAAI;AAAA,IAC/B,cAAc,EAAE,QAAA,EAAU,QAAQ,IAAI;AAAA,EAAA,CACvC,EACA,QAAQ,OAAO,EAAE,OAAO,MAAM,cAAc,KAAA,EAAO;AAAA,EAEtD,WAAW,EACR,OAAO;AAAA,IACN,QAAQ,qBAAqB,QAAQ,wBAAwB;AAAA,IAC7D,MAAM,qBAAqB,QAAQ,wBAAwB;AAAA,IAC3D,OAAO,qBAAqB,QAAQ,wBAAwB;AAAA,IAC5D,QAAQ,qBAAqB,QAAQ,wBAAwB;AAAA,IAC7D,KAAK,qBAAqB,QAAQ,wBAAwB;AAAA,IAC1D,QAAQ,qBAAqB,QAAQ,wBAAwB;AAAA,EAAA,CAC9D,EACA,QAAQ,OAAO;AAAA,IACd,QAAQ,yBAAA;AAAA,IACR,MAAM,yBAAA;AAAA,IACN,OAAO,yBAAA;AAAA,IACP,QAAQ,yBAAA;AAAA,IACR,KAAK,yBAAA;AAAA,IACL,QAAQ,yBAAA;AAAA,EAAyB,EACjC;AAAA,EAEJ,UAAU,EACP,OAAO;AAAA,IACN,SAAS,EAAE,UAAU,QAAQ,IAAI;AAAA,IACjC,uBAAuB,EAAE,UAAU,QAAQ,IAAI;AAAA,IAC/C,yBAAyB,EAAE,UAAU,QAAQ,IAAI;AAAA,IACjD,SAAS,EACN,OAAO;AAAA,MACN,iBAAiB,EAAE,SAAS,QAAQ,EAAE;AAAA,MACtC,cAAc,EAAE,SAAS,QAAQ,EAAE;AAAA,MACnC,gBAAgB,EAAE,MAAM,EAAE,OAAA,CAAQ,EAAE,QAAQ,CAAC,OAAO,SAAS,aAAa,MAAM,CAAC;AAAA,IAAA,CAClF,EACA,QAAQ,OAAO;AAAA,MACd,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,gBAAgB,CAAC,OAAO,SAAS,aAAa,MAAM;AAAA,IAAA,EACpD;AAAA,IACJ,UAAU,EACP,OAAO;AAAA,MACN,sBAAsB,EAAE,SAAS,MAAM,SAAA,EAAW,QAAQ,CAAC;AAAA,MAC3D,gBAAgB,EAAE,SAAS,IAAA,EAAM,IAAI,CAAC,EAAE,QAAQ,CAAC;AAAA,IAAA,CAClD,EACA,QAAQ,OAAO;AAAA,MACd,sBAAsB;AAAA,MACtB,gBAAgB;AAAA,IAAA,EAChB;AAAA,EAAA,CACL,EACA,QAAQ,OAAO;AAAA,IACd,SAAS;AAAA,IACT,uBAAuB;AAAA,IACvB,yBAAyB;AAAA,IACzB,SAAS;AAAA,MACP,iBAAiB;AAAA,MACjB,cAAc;AAAA,MACd,gBAAgB,CAAC,OAAO,SAAS,aAAa,MAAM;AAAA,IAAA;AAAA,IAEtD,UAAU;AAAA,MACR,sBAAsB;AAAA,MACtB,gBAAgB;AAAA,IAAA;AAAA,EAClB,EACA;AAAA,EAEJ,aAAa,EACV,OAAO;AAAA,IACN,mBAAmB,EAChB,OAAO;AAAA,MACN,SAAS,EAAE,UAAU,QAAQ,IAAI;AAAA,MACjC,SAAS,EAAE,SAAS,QAAQ,KAAK;AAAA,MACjC,eAAe,EAAE,UAAU,QAAQ,KAAK;AAAA,MACxC,gBAAgB,EAAE,QAAA,EAAU,QAAQ,KAAK;AAAA,IAAA,CAC1C,EACA,QAAQ,OAAO;AAAA,MACd,SAAS;AAAA,MACT,SAAS;AAAA,MACT,eAAe;AAAA,MACf,gBAAgB;AAAA,IAAA,EAChB;AAAA,EAAA,CACL,EACA,QAAQ,OAAO;AAAA,IACd,mBAAmB;AAAA,MACjB,SAAS;AAAA,MACT,SAAS;AAAA,MACT,eAAe;AAAA,MACf,gBAAgB;AAAA,IAAA;AAAA,EAClB,EACA;AACN,CAAC;"}
|
|
1
|
+
{"version":3,"file":"schema.js","sources":["../../src/config/schema.ts"],"sourcesContent":["import { z } from 'zod';\n\n/**\n * Default values for provider configuration.\n */\nconst PROVIDER_DEFAULTS = {\n enabled: true,\n rate_limit: 3,\n timeout: 30000,\n retries: 3,\n max_results: 10000,\n} as const;\n\n/**\n * Schema for individual provider configuration.\n * Each provider (PubMed, Scopus, etc.) has similar config options.\n */\nexport const ProviderConfigSchema = z.object({\n enabled: z.boolean().default(PROVIDER_DEFAULTS.enabled),\n api_key: z.string().default(''),\n email: z\n .string()\n .refine((val) => val === '' || /^[^@\\s]+@[^@\\s]+\\.[^@\\s]+$/.test(val), {\n message: 'Invalid email',\n })\n .default(''),\n rate_limit: z.number().positive().default(PROVIDER_DEFAULTS.rate_limit),\n timeout: z.number().positive().default(PROVIDER_DEFAULTS.timeout),\n retries: z.number().int().min(0).default(PROVIDER_DEFAULTS.retries),\n max_results: z.number().int().positive().default(PROVIDER_DEFAULTS.max_results),\n inst_token: z.string().default(''),\n});\n\nexport type ProviderConfig = z.infer<typeof ProviderConfigSchema>;\n\n/**\n * Get default provider config object.\n */\nfunction getDefaultProviderConfig(): ProviderConfig {\n return ProviderConfigSchema.parse({});\n}\n\n/**\n * Main configuration schema for search-hub.\n * All fields have defaults, so an empty object is valid input.\n */\nexport const ConfigSchema = z.object({\n session: z\n .object({\n // Empty string means use platform default (resolved in loader.ts)\n directory: z.string().default(''),\n })\n .default(() => ({ directory: '' })),\n\n log: z\n .object({\n level: z.enum(['debug', 'info', 'warn', 'error']).default('info'),\n })\n .default(() => ({ level: 'info' as const })),\n\n output: z\n .object({\n color: z.boolean().default(true),\n progress_bar: z.boolean().default(true),\n })\n .default(() => ({ color: true, progress_bar: true })),\n\n providers: z\n .object({\n pubmed: ProviderConfigSchema.default(getDefaultProviderConfig),\n eric: ProviderConfigSchema.default(getDefaultProviderConfig),\n arxiv: ProviderConfigSchema.default(getDefaultProviderConfig),\n scopus: ProviderConfigSchema.default(getDefaultProviderConfig),\n wos: ProviderConfigSchema.default(getDefaultProviderConfig),\n embase: ProviderConfigSchema.default(getDefaultProviderConfig),\n })\n .default(() => ({\n pubmed: getDefaultProviderConfig(),\n eric: getDefaultProviderConfig(),\n arxiv: getDefaultProviderConfig(),\n scopus: getDefaultProviderConfig(),\n wos: getDefaultProviderConfig(),\n embase: getDefaultProviderConfig(),\n })),\n\n fulltext: z\n .object({\n enabled: z.boolean().default(true),\n auto_convert_markdown: z.boolean().default(true),\n auto_attach_on_register: z.boolean().default(true),\n sources: z\n .object({\n unpaywall_email: z.string().default(''),\n core_api_key: z.string().default(''),\n ncbi_email: z.string().default(''),\n ncbi_tool: z.string().default('search-hub'),\n prefer_sources: z.array(z.string()).default(['pmc', 'arxiv', 'unpaywall', 'core']),\n })\n .default(() => ({\n unpaywall_email: '',\n core_api_key: '',\n ncbi_email: '',\n ncbi_tool: 'search-hub',\n prefer_sources: ['pmc', 'arxiv', 'unpaywall', 'core'],\n })),\n download: z\n .object({\n concurrent_downloads: z.number().int().positive().default(3),\n retry_attempts: z.number().int().min(0).default(3),\n })\n .default(() => ({\n concurrent_downloads: 3,\n retry_attempts: 3,\n })),\n })\n .default(() => ({\n enabled: true,\n auto_convert_markdown: true,\n auto_attach_on_register: true,\n sources: {\n unpaywall_email: '',\n core_api_key: '',\n ncbi_email: '',\n ncbi_tool: 'search-hub',\n prefer_sources: ['pmc', 'arxiv', 'unpaywall', 'core'],\n },\n download: {\n concurrent_downloads: 3,\n retry_attempts: 3,\n },\n })),\n\n integration: z\n .object({\n reference_manager: z\n .object({\n enabled: z.boolean().default(true),\n command: z.string().default('ref'),\n auto_register: z.boolean().default(false),\n with_abstracts: z.boolean().default(false),\n })\n .default(() => ({\n enabled: true,\n command: 'ref',\n auto_register: false,\n with_abstracts: false,\n })),\n })\n .default(() => ({\n reference_manager: {\n enabled: true,\n command: 'ref',\n auto_register: false,\n with_abstracts: false,\n },\n })),\n});\n\nexport type Config = z.infer<typeof ConfigSchema>;\n"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FulltextMeta } from '
|
|
2
|
-
import { FulltextAttachResult } from '
|
|
1
|
+
import { FulltextMeta } from '@ncukondo/academic-fulltext';
|
|
2
|
+
import { FulltextAttachResult } from './types.js';
|
|
3
3
|
/** Files we try to attach, in priority order. */
|
|
4
4
|
export declare const ATTACHABLE_FILES: readonly ["fulltext.pdf", "fulltext.md"];
|
|
5
5
|
/**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach-shared.d.ts","sourceRoot":"","sources":["../../src/integration/attach-shared.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,YAAY,CAAC;AAEvD,iDAAiD;AACjD,eAAO,MAAM,gBAAgB,0CAA2C,CAAC;AAEzE;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,YAAY,EAAE,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,GAAG,SAAS,CAUhG;AAED,iEAAiE;AACjE,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,YAAY,GAAG,IAAI,CAAC;CAC3B;AAED;;;GAGG;AACH,wBAAsB,mBAAmB,CAAC,WAAW,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAwBtF;AAED;;GAEG;AACH,wBAAsB,sBAAsB,CAC1C,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,YAAY,GACjB,OAAO,CAAC,MAAM,EAAE,CAAC,CAcnB;AAED,0CAA0C;AAC1C,MAAM,WAAW,6BAA6B;IAC5C,2DAA2D;IAC3D,WAAW,EAAE,MAAM,CAAC;IACpB,6CAA6C;IAC7C,SAAS,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/B,sDAAsD;IACtD,UAAU,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/D,+EAA+E;IAC/E,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wBAAwB;IACxB,UAAU,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CACvD;AAED;;;GAGG;AACH,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,6BAA6B,GACrC,OAAO,CAAC,oBAAoB,CAAC,CAsE/B"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"attach-shared.js","sources":["../../src/integration/attach-shared.ts"],"sourcesContent":["/**\n * Shared fulltext attach utilities.\n * Used by both the integration attach (register flow) and standalone attach command.\n */\n\nimport { readdir, readFile, access } from 'node:fs/promises';\nimport { constants } from 'node:fs';\nimport { join } from 'node:path';\nimport type { FulltextMeta } from '@ncukondo/academic-fulltext';\nimport type { FulltextAttachResult } from './types.js';\n\n/** Files we try to attach, in priority order. */\nexport const ATTACHABLE_FILES = ['fulltext.pdf', 'fulltext.md'] as const;\n\n/**\n * Find the ref ID matching a fulltext meta entry.\n * Tries DOI first, then PMID.\n */\nexport function findRefId(meta: FulltextMeta, refLookup: Map<string, string>): string | undefined {\n if (meta.doi) {\n const byDoi = refLookup.get(`doi:${meta.doi}`);\n if (byDoi) return byDoi;\n }\n if (meta.pmid) {\n const byPmid = refLookup.get(`pmid:${meta.pmid}`);\n if (byPmid) return byPmid;\n }\n return undefined;\n}\n\n/** A single article directory entry with its loaded metadata. */\nexport interface ArticleEntry {\n dirName: string;\n articleDir: string;\n meta: FulltextMeta | null;\n}\n\n/**\n * Scan a fulltext directory and load metadata for all article subdirectories.\n * Returns an empty array if the directory doesn't exist.\n */\nexport async function loadFulltextEntries(fulltextDir: string): Promise<ArticleEntry[]> {\n let dirNames: string[];\n try {\n const dirEntries = await readdir(fulltextDir, { withFileTypes: true });\n dirNames = dirEntries\n .filter((e) => e.isDirectory())\n .map((e) => e.name);\n } catch {\n return [];\n }\n\n const entries: ArticleEntry[] = [];\n for (const dirName of dirNames) {\n const articleDir = join(fulltextDir, dirName);\n try {\n const raw = await readFile(join(articleDir, 'meta.json'), 'utf-8');\n const meta = JSON.parse(raw) as FulltextMeta;\n entries.push({ dirName, articleDir, meta });\n } catch {\n // Will be handled as a failed entry by the caller\n entries.push({ dirName, articleDir, meta: null });\n }\n }\n return entries;\n}\n\n/**\n * Determine which attachable files exist for an article, verifying on disk.\n */\nexport async function resolveAttachableFiles(\n articleDir: string,\n meta: FulltextMeta,\n): Promise<string[]> {\n const filesToAttach: string[] = [];\n for (const filename of ATTACHABLE_FILES) {\n const fileKey = filename === 'fulltext.pdf' ? 'pdf' : 'markdown';\n if (meta.files[fileKey]) {\n try {\n await access(join(articleDir, filename), constants.R_OK);\n filesToAttach.push(filename);\n } catch {\n // File recorded in meta but not on disk — skip\n }\n }\n }\n return filesToAttach;\n}\n\n/** Options for processFulltextEntries. */\nexport interface ProcessFulltextEntriesOptions {\n /** Path to the fulltext directory (sessionDir/fulltext) */\n fulltextDir: string;\n /** Lookup map from identifiers to ref IDs */\n refLookup: Map<string, string>;\n /** Function to attach a single file to a ref entry */\n attachFile: (refId: string, filePath: string) => Promise<void>;\n /** If true, skip actual attach calls and just record what would be attached */\n dryRun?: boolean;\n /** Progress callback */\n onProgress?: (current: number, total: number) => void;\n}\n\n/**\n * Shared attach loop: scan fulltext entries, match to refs, and attach files.\n * Both the standalone and integrated attach commands delegate to this function.\n */\nexport async function processFulltextEntries(\n options: ProcessFulltextEntriesOptions,\n): Promise<FulltextAttachResult> {\n const { fulltextDir, refLookup, attachFile, dryRun = false, onProgress } = options;\n\n const result: FulltextAttachResult = {\n summary: { total: 0, attached: 0, skipped: 0, failed: 0 },\n attached: [],\n skipped: [],\n failed: [],\n };\n\n const entries = await loadFulltextEntries(fulltextDir);\n result.summary.total = entries.length;\n\n for (let i = 0; i < entries.length; i++) {\n const entry = entries[i]!;\n const { dirName, articleDir, meta } = entry;\n\n if (onProgress) {\n onProgress(i + 1, entries.length);\n }\n\n if (!meta) {\n result.summary.failed++;\n result.failed.push({\n dirName,\n reason: 'meta_read_error',\n error: 'Failed to read meta.json',\n });\n continue;\n }\n\n const refId = findRefId(meta, refLookup);\n if (!refId) {\n result.summary.skipped++;\n result.skipped.push({ dirName, reason: 'not_in_ref' });\n continue;\n }\n\n const filesToAttach = await resolveAttachableFiles(articleDir, meta);\n\n if (filesToAttach.length === 0) {\n result.summary.skipped++;\n result.skipped.push({ dirName, reason: 'no_files' });\n continue;\n }\n\n if (dryRun) {\n result.summary.attached++;\n result.attached.push({ refId, files: filesToAttach });\n continue;\n }\n\n try {\n for (const filename of filesToAttach) {\n const filePath = join(articleDir, filename);\n await attachFile(refId, filePath);\n }\n result.summary.attached++;\n result.attached.push({ refId, files: filesToAttach });\n } catch (error) {\n result.summary.failed++;\n result.failed.push({\n dirName,\n reason: 'attach_error',\n error: error instanceof Error ? error.message : 'Unknown error',\n });\n }\n }\n\n return result;\n}\n"],"names":[],"mappings":"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"}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { join } from "node:path";
|
|
2
2
|
import { refFulltextAttach } from "./ref-cli.js";
|
|
3
|
-
import { processFulltextEntries } from "
|
|
3
|
+
import { processFulltextEntries } from "./attach-shared.js";
|
|
4
4
|
function extractDoi(source) {
|
|
5
5
|
if (source.startsWith("10.")) return source;
|
|
6
6
|
if (source.startsWith("doi:")) return source.slice(4);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"fulltext-attach.js","sources":["../../src/integration/fulltext-attach.ts"],"sourcesContent":["/**\n * Fulltext attach logic for reference-manager integration.\n * Attaches fulltext files (PDF, Markdown) from session fulltext directories\n * to corresponding entries in the reference-manager library.\n */\n\nimport { join } from 'node:path';\nimport type { FulltextAttachResult } from './types.js';\nimport { refFulltextAttach, type RefCliOptions } from './ref-cli.js';\nimport { processFulltextEntries } from '
|
|
1
|
+
{"version":3,"file":"fulltext-attach.js","sources":["../../src/integration/fulltext-attach.ts"],"sourcesContent":["/**\n * Fulltext attach logic for reference-manager integration.\n * Attaches fulltext files (PDF, Markdown) from session fulltext directories\n * to corresponding entries in the reference-manager library.\n */\n\nimport { join } from 'node:path';\nimport type { FulltextAttachResult } from './types.js';\nimport { refFulltextAttach, type RefCliOptions } from './ref-cli.js';\nimport { processFulltextEntries } from './attach-shared.js';\n\n/**\n * Options for attachFulltexts function.\n */\nexport interface AttachFulltextsOptions {\n /** Path to the session directory */\n sessionDir: string;\n /** Path to the reference library file */\n libraryPath: string;\n /** List of refs that were added/exist in the library, with source identifiers */\n addedRefs: Array<{ id: string; source: string }>;\n /** Progress callback */\n onProgress?: (current: number, total: number) => void;\n}\n\n/**\n * Extract DOI from a source string.\n * Sources look like \"10.1234/test\" (bare DOI) or \"doi:10.1234/test\".\n */\nfunction extractDoi(source: string): string | undefined {\n if (source.startsWith('10.')) return source;\n if (source.startsWith('doi:')) return source.slice(4);\n return undefined;\n}\n\n/**\n * Extract PMID from a source string.\n * Sources look like \"pmid:12345678\".\n */\nfunction extractPmid(source: string): string | undefined {\n if (source.startsWith('pmid:')) return source.slice(5);\n return undefined;\n}\n\n/**\n * Build a lookup map from identifiers to ref IDs.\n * Keys are normalized: \"doi:10.1234/test\" and \"pmid:12345678\".\n */\nfunction buildRefLookup(addedRefs: Array<{ id: string; source: string }>): Map<string, string> {\n const lookup = new Map<string, string>();\n for (const ref of addedRefs) {\n const doi = extractDoi(ref.source);\n if (doi) {\n lookup.set(`doi:${doi}`, ref.id);\n }\n const pmid = extractPmid(ref.source);\n if (pmid) {\n lookup.set(`pmid:${pmid}`, ref.id);\n }\n }\n return lookup;\n}\n\n/**\n * Attach fulltext files from session fulltext directories to reference-manager entries.\n *\n * For each article directory in sessionDir/fulltext/:\n * 1. Load meta.json to get identifiers\n * 2. Match to a ref entry by DOI or PMID\n * 3. Attach available fulltext files (PDF, Markdown)\n * 4. Record results\n */\nexport async function attachFulltexts(\n options: AttachFulltextsOptions,\n): Promise<FulltextAttachResult> {\n const { sessionDir, libraryPath, addedRefs, onProgress } = options;\n const fulltextDir = join(sessionDir, 'fulltext');\n const refCliOptions: RefCliOptions = { libraryPath };\n const refLookup = buildRefLookup(addedRefs);\n\n return processFulltextEntries({\n fulltextDir,\n refLookup,\n attachFile: (refId, filePath) => refFulltextAttach(refId, filePath, refCliOptions),\n ...(onProgress ? { onProgress } : {}),\n });\n}\n"],"names":[],"mappings":";;;AA6BA,SAAS,WAAW,QAAoC;AACtD,MAAI,OAAO,WAAW,KAAK,EAAG,QAAO;AACrC,MAAI,OAAO,WAAW,MAAM,EAAG,QAAO,OAAO,MAAM,CAAC;AACpD,SAAO;AACT;AAMA,SAAS,YAAY,QAAoC;AACvD,MAAI,OAAO,WAAW,OAAO,EAAG,QAAO,OAAO,MAAM,CAAC;AACrD,SAAO;AACT;AAMA,SAAS,eAAe,WAAuE;AAC7F,QAAM,6BAAa,IAAA;AACnB,aAAW,OAAO,WAAW;AAC3B,UAAM,MAAM,WAAW,IAAI,MAAM;AACjC,QAAI,KAAK;AACP,aAAO,IAAI,OAAO,GAAG,IAAI,IAAI,EAAE;AAAA,IACjC;AACA,UAAM,OAAO,YAAY,IAAI,MAAM;AACnC,QAAI,MAAM;AACR,aAAO,IAAI,QAAQ,IAAI,IAAI,IAAI,EAAE;AAAA,IACnC;AAAA,EACF;AACA,SAAO;AACT;AAWA,eAAsB,gBACpB,SAC+B;AAC/B,QAAM,EAAE,YAAY,aAAa,WAAW,eAAe;AAC3D,QAAM,cAAc,KAAK,YAAY,UAAU;AAC/C,QAAM,gBAA+B,EAAE,YAAA;AACvC,QAAM,YAAY,eAAe,SAAS;AAE1C,SAAO,uBAAuB;AAAA,IAC5B;AAAA,IACA;AAAA,IACA,YAAY,CAAC,OAAO,aAAa,kBAAkB,OAAO,UAAU,aAAa;AAAA,IACjF,GAAI,aAAa,EAAE,eAAe,CAAA;AAAA,EAAC,CACpC;AACH;"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"citation-key.js","sources":["../../../../../node_modules/@ncukondo/academic-fulltext/dist/citation-key.js"],"sourcesContent":["/**\n * Citation key generation for fulltext directories.\n */\nimport { randomUUID } from \"node:crypto\";\nimport anyAscii from \"any-ascii\";\n/**\n * Generate a collision suffix: a, b, ..., z, aa, ab, ...\n */\nfunction collisionSuffix(index) {\n let result = \"\";\n let n = index;\n do {\n result = String.fromCodePoint(97 + (n % 26)) + result;\n n = Math.floor(n / 26) - 1;\n } while (n >= 0);\n return result;\n}\n/** CJK Unified Ideographs range: U+4E00–U+9FFF */\nconst CJK_REGEX = /[\\u4e00-\\u9fff]/;\n/**\n * Extract the family name portion from an author string.\n * Handles formats like \"Smith, J.\" → \"Smith\", \"Smith\" → \"Smith\".\n */\nfunction extractFamilyName(author) {\n const commaIndex = author.indexOf(\",\");\n if (commaIndex >= 0) {\n return author.slice(0, commaIndex).trim();\n }\n return author.trim();\n}\n/**\n * Generate a citation key from author and year.\n * Format: {family-name-lowercase}{year}\n * With collision handling via letter suffixes (a, b, c, ...).\n */\nexport function generateCitationKey(author, year, existingKeys) {\n // Extract and normalize author\n const rawFamily = author?.trim() ? extractFamilyName(author) : \"unknown\";\n // CJK characters cannot be accurately transliterated to the correct reading\n // (any-ascii maps them to Chinese pinyin, not Japanese romaji etc.)\n // Fall back to 'unknown' for names containing CJK ideographs.\n const normalizedFamily = CJK_REGEX.test(rawFamily)\n ? \"unknown\"\n : anyAscii(rawFamily)\n .toLowerCase()\n .replace(/[^a-z]/g, \"\") || \"unknown\";\n // Normalize year\n const normalizedYear = year?.trim() || \"0000\";\n const baseKey = `${normalizedFamily}${normalizedYear}`;\n // Handle collisions\n if (!existingKeys || !existingKeys.includes(baseKey)) {\n return baseKey;\n }\n // Find the first available suffix\n for (let i = 0;; i++) {\n const candidateKey = `${baseKey}${collisionSuffix(i)}`;\n if (!existingKeys.includes(candidateKey)) {\n return candidateKey;\n }\n }\n}\n/**\n * Generate a directory name from a citation key.\n * Format: {citationKey}-{uuid8}\n */\nexport function generateDirName(citationKey, uuid) {\n const id = uuid ?? randomUUID();\n const uuid8 = id.slice(0, 8);\n return `${citationKey}-${uuid8}`;\n}\n//# sourceMappingURL=citation-key.js.map"],"names":[],"mappings":";;AAQA,SAAS,gBAAgB,OAAO;AAC5B,MAAI,SAAS;AACb,MAAI,IAAI;AACR,KAAG;AACC,aAAS,OAAO,cAAc,KAAM,IAAI,EAAG,IAAI;AAC/C,QAAI,KAAK,MAAM,IAAI,EAAE,IAAI;AAAA,EAC7B,SAAS,KAAK;AACd,SAAO;AACX;AAEA,MAAM,YAAY;AAKlB,SAAS,kBAAkB,QAAQ;AAC/B,QAAM,aAAa,OAAO,QAAQ,GAAG;AACrC,MAAI,cAAc,GAAG;AACjB,WAAO,OAAO,MAAM,GAAG,UAAU,EAAE,KAAI;AAAA,EAC3C;AACA,SAAO,OAAO,KAAI;AACtB;AAMO,SAAS,oBAAoB,QAAQ,MAAM,cAAc;AAE5D,QAAM,YAAY,QAAQ,KAAI,IAAK,kBAAkB,MAAM,IAAI;AAI/D,QAAM,mBAAmB,UAAU,KAAK,SAAS,IAC3C,YACA,SAAS,SAAS,EACf,YAAW,EACX,QAAQ,WAAW,EAAE,KAAK;AAEnC,QAAM,iBAAiB,MAAM,KAAI,KAAM;AACvC,QAAM,UAAU,GAAG,gBAAgB,GAAG,cAAc;AAEpD,MAAI,CAAC,gBAAgB,CAAC,aAAa,SAAS,OAAO,GAAG;AAClD,WAAO;AAAA,EACX;AAEA,WAAS,IAAI,KAAI,KAAK;AAClB,UAAM,eAAe,GAAG,OAAO,GAAG,gBAAgB,CAAC,CAAC;AACpD,QAAI,CAAC,aAAa,SAAS,YAAY,GAAG;AACtC,aAAO;AAAA,IACX;AAAA,EACJ;AACJ;AAKO,SAAS,gBAAgB,aAAa,MAAM;AAC/C,QAAM,KAAK,QAAQ,WAAU;AAC7B,QAAM,QAAQ,GAAG,MAAM,GAAG,CAAC;AAC3B,SAAO,GAAG,WAAW,IAAI,KAAK;AAClC;","x_google_ignoreList":[0]}
|