@zenalexa/unicli 0.220.1 → 0.221.0
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/AGENTS.md +28 -6
- package/README.md +8 -8
- package/README.zh-CN.md +8 -8
- package/dist/adapters/bilibili/comments.js +66 -4
- package/dist/adapters/bilibili/comments.js.map +1 -1
- package/dist/adapters/bilibili/compat.js +2 -2
- package/dist/adapters/bilibili/compat.js.map +1 -1
- package/dist/adapters/bilibili/download.js +4 -4
- package/dist/adapters/bilibili/download.js.map +1 -1
- package/dist/adapters/bilibili/wbi.d.ts.map +1 -1
- package/dist/adapters/bilibili/wbi.js +3 -3
- package/dist/adapters/bilibili/wbi.js.map +1 -1
- package/dist/adapters/cipo/_shared.d.ts +21 -0
- package/dist/adapters/cipo/_shared.d.ts.map +1 -0
- package/dist/adapters/cipo/_shared.js +67 -0
- package/dist/adapters/cipo/_shared.js.map +1 -0
- package/dist/adapters/cipo/get.d.ts +19 -0
- package/dist/adapters/cipo/get.d.ts.map +1 -0
- package/dist/adapters/cipo/get.js +140 -0
- package/dist/adapters/cipo/get.js.map +1 -0
- package/dist/adapters/cipo/legal-status.d.ts +19 -0
- package/dist/adapters/cipo/legal-status.d.ts.map +1 -0
- package/dist/adapters/cipo/legal-status.js +111 -0
- package/dist/adapters/cipo/legal-status.js.map +1 -0
- package/dist/adapters/cipo/search.d.ts +20 -0
- package/dist/adapters/cipo/search.d.ts.map +1 -0
- package/dist/adapters/cipo/search.js +148 -0
- package/dist/adapters/cipo/search.js.map +1 -0
- package/dist/adapters/cnipa/_shared.d.ts +47 -0
- package/dist/adapters/cnipa/_shared.d.ts.map +1 -0
- package/dist/adapters/cnipa/_shared.js +97 -0
- package/dist/adapters/cnipa/_shared.js.map +1 -0
- package/dist/adapters/cnipa/get.d.ts +19 -0
- package/dist/adapters/cnipa/get.d.ts.map +1 -0
- package/dist/adapters/cnipa/get.js +149 -0
- package/dist/adapters/cnipa/get.js.map +1 -0
- package/dist/adapters/cnipa/legal-status.d.ts +19 -0
- package/dist/adapters/cnipa/legal-status.d.ts.map +1 -0
- package/dist/adapters/cnipa/legal-status.js +119 -0
- package/dist/adapters/cnipa/legal-status.js.map +1 -0
- package/dist/adapters/cnipa/search.d.ts +21 -0
- package/dist/adapters/cnipa/search.d.ts.map +1 -0
- package/dist/adapters/cnipa/search.js +170 -0
- package/dist/adapters/cnipa/search.js.map +1 -0
- package/dist/adapters/espacenet/_shared.d.ts +21 -0
- package/dist/adapters/espacenet/_shared.d.ts.map +1 -0
- package/dist/adapters/espacenet/_shared.js +67 -0
- package/dist/adapters/espacenet/_shared.js.map +1 -0
- package/dist/adapters/espacenet/family.d.ts +19 -0
- package/dist/adapters/espacenet/family.d.ts.map +1 -0
- package/dist/adapters/espacenet/family.js +118 -0
- package/dist/adapters/espacenet/family.js.map +1 -0
- package/dist/adapters/espacenet/get.d.ts +19 -0
- package/dist/adapters/espacenet/get.d.ts.map +1 -0
- package/dist/adapters/espacenet/get.js +130 -0
- package/dist/adapters/espacenet/get.js.map +1 -0
- package/dist/adapters/espacenet/legal-status.d.ts +19 -0
- package/dist/adapters/espacenet/legal-status.d.ts.map +1 -0
- package/dist/adapters/espacenet/legal-status.js +110 -0
- package/dist/adapters/espacenet/legal-status.js.map +1 -0
- package/dist/adapters/espacenet/search.d.ts +20 -0
- package/dist/adapters/espacenet/search.d.ts.map +1 -0
- package/dist/adapters/espacenet/search.js +165 -0
- package/dist/adapters/espacenet/search.js.map +1 -0
- package/dist/adapters/facebook/subtitles.d.ts +9 -0
- package/dist/adapters/facebook/subtitles.d.ts.map +1 -0
- package/dist/adapters/facebook/subtitles.js +42 -0
- package/dist/adapters/facebook/subtitles.js.map +1 -0
- package/dist/adapters/fips/_shared.d.ts +21 -0
- package/dist/adapters/fips/_shared.d.ts.map +1 -0
- package/dist/adapters/fips/_shared.js +77 -0
- package/dist/adapters/fips/_shared.js.map +1 -0
- package/dist/adapters/fips/get.d.ts +19 -0
- package/dist/adapters/fips/get.d.ts.map +1 -0
- package/dist/adapters/fips/get.js +139 -0
- package/dist/adapters/fips/get.js.map +1 -0
- package/dist/adapters/fips/search.d.ts +20 -0
- package/dist/adapters/fips/search.d.ts.map +1 -0
- package/dist/adapters/fips/search.js +148 -0
- package/dist/adapters/fips/search.js.map +1 -0
- package/dist/adapters/freepatentsonline-web/_shared.d.ts +72 -0
- package/dist/adapters/freepatentsonline-web/_shared.d.ts.map +1 -0
- package/dist/adapters/freepatentsonline-web/_shared.js +216 -0
- package/dist/adapters/freepatentsonline-web/_shared.js.map +1 -0
- package/dist/adapters/freepatentsonline-web/get.d.ts +21 -0
- package/dist/adapters/freepatentsonline-web/get.d.ts.map +1 -0
- package/dist/adapters/freepatentsonline-web/get.js +127 -0
- package/dist/adapters/freepatentsonline-web/get.js.map +1 -0
- package/dist/adapters/freepatentsonline-web/search.d.ts +22 -0
- package/dist/adapters/freepatentsonline-web/search.d.ts.map +1 -0
- package/dist/adapters/freepatentsonline-web/search.js +149 -0
- package/dist/adapters/freepatentsonline-web/search.js.map +1 -0
- package/dist/adapters/google-patents-web/_shared.d.ts +110 -0
- package/dist/adapters/google-patents-web/_shared.d.ts.map +1 -0
- package/dist/adapters/google-patents-web/_shared.js +164 -0
- package/dist/adapters/google-patents-web/_shared.js.map +1 -0
- package/dist/adapters/google-patents-web/get.d.ts +36 -0
- package/dist/adapters/google-patents-web/get.d.ts.map +1 -0
- package/dist/adapters/google-patents-web/get.js +187 -0
- package/dist/adapters/google-patents-web/get.js.map +1 -0
- package/dist/adapters/google-patents-web/search.d.ts +23 -0
- package/dist/adapters/google-patents-web/search.d.ts.map +1 -0
- package/dist/adapters/google-patents-web/search.js +169 -0
- package/dist/adapters/google-patents-web/search.js.map +1 -0
- package/dist/adapters/inpi-br/_shared.d.ts +21 -0
- package/dist/adapters/inpi-br/_shared.d.ts.map +1 -0
- package/dist/adapters/inpi-br/_shared.js +67 -0
- package/dist/adapters/inpi-br/_shared.js.map +1 -0
- package/dist/adapters/inpi-br/get.d.ts +19 -0
- package/dist/adapters/inpi-br/get.d.ts.map +1 -0
- package/dist/adapters/inpi-br/get.js +142 -0
- package/dist/adapters/inpi-br/get.js.map +1 -0
- package/dist/adapters/inpi-br/search.d.ts +20 -0
- package/dist/adapters/inpi-br/search.d.ts.map +1 -0
- package/dist/adapters/inpi-br/search.js +154 -0
- package/dist/adapters/inpi-br/search.js.map +1 -0
- package/dist/adapters/instagram/subtitles.d.ts +9 -0
- package/dist/adapters/instagram/subtitles.d.ts.map +1 -0
- package/dist/adapters/instagram/subtitles.js +42 -0
- package/dist/adapters/instagram/subtitles.js.map +1 -0
- package/dist/adapters/mastodon/statuses.d.ts +40 -0
- package/dist/adapters/mastodon/statuses.d.ts.map +1 -0
- package/dist/adapters/mastodon/statuses.js +153 -0
- package/dist/adapters/mastodon/statuses.js.map +1 -0
- package/dist/adapters/reddit/comments.d.ts +9 -0
- package/dist/adapters/reddit/comments.d.ts.map +1 -0
- package/dist/adapters/reddit/comments.js +124 -0
- package/dist/adapters/reddit/comments.js.map +1 -0
- package/dist/adapters/threads/post.d.ts +32 -0
- package/dist/adapters/threads/post.d.ts.map +1 -0
- package/dist/adapters/threads/post.js +287 -0
- package/dist/adapters/threads/post.js.map +1 -0
- package/dist/adapters/tiktok/subtitles.d.ts +9 -0
- package/dist/adapters/tiktok/subtitles.d.ts.map +1 -0
- package/dist/adapters/tiktok/subtitles.js +42 -0
- package/dist/adapters/tiktok/subtitles.js.map +1 -0
- package/dist/adapters/twitter/accept.js +2 -2
- package/dist/adapters/twitter/accept.js.map +1 -1
- package/dist/adapters/twitter/browser-fallback.d.ts +26 -0
- package/dist/adapters/twitter/browser-fallback.d.ts.map +1 -0
- package/dist/adapters/twitter/browser-fallback.js +93 -0
- package/dist/adapters/twitter/browser-fallback.js.map +1 -0
- package/dist/adapters/twitter/browser-state.d.ts +11 -0
- package/dist/adapters/twitter/browser-state.d.ts.map +1 -0
- package/dist/adapters/twitter/browser-state.js +46 -0
- package/dist/adapters/twitter/browser-state.js.map +1 -0
- package/dist/adapters/twitter/client.d.ts.map +1 -1
- package/dist/adapters/twitter/client.js +36 -13
- package/dist/adapters/twitter/client.js.map +1 -1
- package/dist/adapters/twitter/reply-dm.js +2 -2
- package/dist/adapters/twitter/reply-dm.js.map +1 -1
- package/dist/adapters/twitter/reply.js +1 -0
- package/dist/adapters/twitter/reply.js.map +1 -1
- package/dist/adapters/twitter/search.js +11 -18
- package/dist/adapters/twitter/search.js.map +1 -1
- package/dist/adapters/twitter/thread.d.ts +14 -0
- package/dist/adapters/twitter/thread.d.ts.map +1 -1
- package/dist/adapters/twitter/thread.js +28 -2
- package/dist/adapters/twitter/thread.js.map +1 -1
- package/dist/adapters/twitter/trending.js +13 -59
- package/dist/adapters/twitter/trending.js.map +1 -1
- package/dist/adapters/xiaohongshu/browser-state.d.ts +19 -0
- package/dist/adapters/xiaohongshu/browser-state.d.ts.map +1 -0
- package/dist/adapters/xiaohongshu/browser-state.js +67 -0
- package/dist/adapters/xiaohongshu/browser-state.js.map +1 -0
- package/dist/adapters/xiaohongshu/comments.js +28 -5
- package/dist/adapters/xiaohongshu/comments.js.map +1 -1
- package/dist/adapters/xiaohongshu/download.js +49 -11
- package/dist/adapters/xiaohongshu/download.js.map +1 -1
- package/dist/adapters/xiaohongshu/search.d.ts.map +1 -1
- package/dist/adapters/xiaohongshu/search.js +11 -5
- package/dist/adapters/xiaohongshu/search.js.map +1 -1
- package/dist/adapters/xiaohongshu/trending.d.ts +9 -0
- package/dist/adapters/xiaohongshu/trending.d.ts.map +1 -0
- package/dist/adapters/xiaohongshu/trending.js +94 -0
- package/dist/adapters/xiaohongshu/trending.js.map +1 -0
- package/dist/adapters/youtube/comments.d.ts +80 -0
- package/dist/adapters/youtube/comments.d.ts.map +1 -1
- package/dist/adapters/youtube/comments.js +108 -12
- package/dist/adapters/youtube/comments.js.map +1 -1
- package/dist/adapters/youtube/subtitles.d.ts +9 -0
- package/dist/adapters/youtube/subtitles.d.ts.map +1 -0
- package/dist/adapters/youtube/subtitles.js +42 -0
- package/dist/adapters/youtube/subtitles.js.map +1 -0
- package/dist/adapters/yt-dlp/subtitles.d.ts +9 -0
- package/dist/adapters/yt-dlp/subtitles.d.ts.map +1 -0
- package/dist/adapters/yt-dlp/subtitles.js +41 -0
- package/dist/adapters/yt-dlp/subtitles.js.map +1 -0
- package/dist/adapters/zhihu/answer-detail.d.ts +39 -0
- package/dist/adapters/zhihu/answer-detail.d.ts.map +1 -0
- package/dist/adapters/zhihu/answer-detail.js +204 -0
- package/dist/adapters/zhihu/answer-detail.js.map +1 -0
- package/dist/adapters/zhihu/comment.d.ts +9 -0
- package/dist/adapters/zhihu/comment.d.ts.map +1 -0
- package/dist/adapters/zhihu/comment.js +149 -0
- package/dist/adapters/zhihu/comment.js.map +1 -0
- package/dist/adapters/zhihu/recommend.d.ts +36 -0
- package/dist/adapters/zhihu/recommend.d.ts.map +1 -0
- package/dist/adapters/zhihu/recommend.js +151 -0
- package/dist/adapters/zhihu/recommend.js.map +1 -0
- package/dist/browser/bridge.d.ts.map +1 -1
- package/dist/browser/bridge.js +14 -3
- package/dist/browser/bridge.js.map +1 -1
- package/dist/browser/daemon-client.d.ts +6 -0
- package/dist/browser/daemon-client.d.ts.map +1 -1
- package/dist/browser/daemon-client.js +75 -15
- package/dist/browser/daemon-client.js.map +1 -1
- package/dist/browser/daemon.js +39 -15
- package/dist/browser/daemon.js.map +1 -1
- package/dist/browser/protocol.d.ts +1 -0
- package/dist/browser/protocol.d.ts.map +1 -1
- package/dist/browser/protocol.js +1 -0
- package/dist/browser/protocol.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +6 -0
- package/dist/cli.js.map +1 -1
- package/dist/commands/approvals.d.ts.map +1 -1
- package/dist/commands/approvals.js +1 -37
- package/dist/commands/approvals.js.map +1 -1
- package/dist/commands/browser/index.d.ts.map +1 -1
- package/dist/commands/browser/index.js +7 -2
- package/dist/commands/browser/index.js.map +1 -1
- package/dist/commands/daemon.d.ts.map +1 -1
- package/dist/commands/daemon.js +7 -3
- package/dist/commands/daemon.js.map +1 -1
- package/dist/commands/dispatch.d.ts.map +1 -1
- package/dist/commands/dispatch.js +27 -3
- package/dist/commands/dispatch.js.map +1 -1
- package/dist/commands/patent-doctor.d.ts +48 -0
- package/dist/commands/patent-doctor.d.ts.map +1 -0
- package/dist/commands/patent-doctor.js +109 -0
- package/dist/commands/patent-doctor.js.map +1 -0
- package/dist/commands/patent.d.ts +78 -0
- package/dist/commands/patent.d.ts.map +1 -0
- package/dist/commands/patent.js +919 -0
- package/dist/commands/patent.js.map +1 -0
- package/dist/commands/social.d.ts +19 -0
- package/dist/commands/social.d.ts.map +1 -0
- package/dist/commands/social.js +236 -0
- package/dist/commands/social.js.map +1 -0
- package/dist/core/registry.d.ts +1 -1
- package/dist/core/registry.d.ts.map +1 -1
- package/dist/core/registry.js +11 -2
- package/dist/core/registry.js.map +1 -1
- package/dist/discovery/loader.d.ts.map +1 -1
- package/dist/discovery/loader.js +4 -0
- package/dist/discovery/loader.js.map +1 -1
- package/dist/engine/approval-presenter.d.ts +10 -0
- package/dist/engine/approval-presenter.d.ts.map +1 -0
- package/dist/engine/approval-presenter.js +45 -0
- package/dist/engine/approval-presenter.js.map +1 -0
- package/dist/engine/approval-store.d.ts +4 -0
- package/dist/engine/approval-store.d.ts.map +1 -1
- package/dist/engine/approval-store.js +85 -11
- package/dist/engine/approval-store.js.map +1 -1
- package/dist/engine/auth/oauth2-cc.d.ts +67 -0
- package/dist/engine/auth/oauth2-cc.d.ts.map +1 -0
- package/dist/engine/auth/oauth2-cc.js +120 -0
- package/dist/engine/auth/oauth2-cc.js.map +1 -0
- package/dist/engine/cookies.d.ts +10 -0
- package/dist/engine/cookies.d.ts.map +1 -1
- package/dist/engine/cookies.js +64 -0
- package/dist/engine/cookies.js.map +1 -1
- package/dist/engine/download.d.ts +5 -0
- package/dist/engine/download.d.ts.map +1 -1
- package/dist/engine/download.js +11 -4
- package/dist/engine/download.js.map +1 -1
- package/dist/engine/executor.d.ts +1 -0
- package/dist/engine/executor.d.ts.map +1 -1
- package/dist/engine/executor.js +25 -0
- package/dist/engine/executor.js.map +1 -1
- package/dist/engine/framework.d.ts +5 -5
- package/dist/engine/framework.js +5 -5
- package/dist/engine/harden.d.ts +1 -1
- package/dist/engine/harden.js +1 -1
- package/dist/engine/kernel/stages.d.ts.map +1 -1
- package/dist/engine/kernel/stages.js +2 -1
- package/dist/engine/kernel/stages.js.map +1 -1
- package/dist/engine/normalizer/patent-envelope.d.ts +61 -0
- package/dist/engine/normalizer/patent-envelope.d.ts.map +1 -0
- package/dist/engine/normalizer/patent-envelope.js +132 -0
- package/dist/engine/normalizer/patent-envelope.js.map +1 -0
- package/dist/engine/research.d.ts +5 -7
- package/dist/engine/research.d.ts.map +1 -1
- package/dist/engine/research.js +6 -9
- package/dist/engine/research.js.map +1 -1
- package/dist/engine/steps/browser-helpers.d.ts +2 -2
- package/dist/engine/steps/browser-helpers.d.ts.map +1 -1
- package/dist/engine/steps/browser-helpers.js +39 -16
- package/dist/engine/steps/browser-helpers.js.map +1 -1
- package/dist/engine/steps/download.d.ts +1 -0
- package/dist/engine/steps/download.d.ts.map +1 -1
- package/dist/engine/steps/download.js +3 -1
- package/dist/engine/steps/download.js.map +1 -1
- package/dist/engine/steps/index.d.ts +2 -0
- package/dist/engine/steps/index.d.ts.map +1 -1
- package/dist/engine/steps/index.js +2 -0
- package/dist/engine/steps/index.js.map +1 -1
- package/dist/engine/steps/oauth2-token.d.ts +41 -0
- package/dist/engine/steps/oauth2-token.d.ts.map +1 -0
- package/dist/engine/steps/oauth2-token.js +115 -0
- package/dist/engine/steps/oauth2-token.js.map +1 -0
- package/dist/engine/steps/select-xml.d.ts +34 -0
- package/dist/engine/steps/select-xml.d.ts.map +1 -0
- package/dist/engine/steps/select-xml.js +222 -0
- package/dist/engine/steps/select-xml.js.map +1 -0
- package/dist/engine/template.d.ts.map +1 -1
- package/dist/engine/template.js +7 -0
- package/dist/engine/template.js.map +1 -1
- package/dist/engine/transport/mcp-browser.d.ts +128 -0
- package/dist/engine/transport/mcp-browser.d.ts.map +1 -0
- package/dist/engine/transport/mcp-browser.js +120 -0
- package/dist/engine/transport/mcp-browser.js.map +1 -0
- package/dist/fast-path/handlers/approvals.d.ts +11 -0
- package/dist/fast-path/handlers/approvals.d.ts.map +1 -0
- package/dist/fast-path/handlers/approvals.js +136 -0
- package/dist/fast-path/handlers/approvals.js.map +1 -0
- package/dist/fast-path/manifest.d.ts +1 -0
- package/dist/fast-path/manifest.d.ts.map +1 -1
- package/dist/fast-path/manifest.js.map +1 -1
- package/dist/fast-path.d.ts.map +1 -1
- package/dist/fast-path.js +3 -0
- package/dist/fast-path.js.map +1 -1
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +38 -0
- package/dist/index.js.map +1 -0
- package/dist/manifest-compact.txt +3 -3
- package/dist/manifest-search.json +1 -1
- package/dist/manifest.json +2239 -176
- package/dist/output/auth-guidance.d.ts +14 -0
- package/dist/output/auth-guidance.d.ts.map +1 -0
- package/dist/output/auth-guidance.js +50 -0
- package/dist/output/auth-guidance.js.map +1 -0
- package/dist/output/error-map.d.ts +1 -1
- package/dist/output/error-map.d.ts.map +1 -1
- package/dist/output/error-map.js +28 -4
- package/dist/output/error-map.js.map +1 -1
- package/dist/output/next-actions.d.ts.map +1 -1
- package/dist/output/next-actions.js +19 -3
- package/dist/output/next-actions.js.map +1 -1
- package/dist/registry.d.ts +18 -1
- package/dist/registry.d.ts.map +1 -1
- package/dist/registry.js +5 -0
- package/dist/registry.js.map +1 -1
- package/dist/social/browser-errors.d.ts +13 -0
- package/dist/social/browser-errors.d.ts.map +1 -0
- package/dist/social/browser-errors.js +36 -0
- package/dist/social/browser-errors.js.map +1 -0
- package/dist/social/capabilities.d.ts +29 -0
- package/dist/social/capabilities.d.ts.map +1 -0
- package/dist/social/capabilities.js +448 -0
- package/dist/social/capabilities.js.map +1 -0
- package/dist/social/comments.d.ts +26 -0
- package/dist/social/comments.d.ts.map +1 -0
- package/dist/social/comments.js +97 -0
- package/dist/social/comments.js.map +1 -0
- package/dist/social/video-text.d.ts +27 -0
- package/dist/social/video-text.d.ts.map +1 -0
- package/dist/social/video-text.js +140 -0
- package/dist/social/video-text.js.map +1 -0
- package/dist/types/patent.d.ts +160 -0
- package/dist/types/patent.d.ts.map +1 -0
- package/dist/types/patent.js +16 -0
- package/dist/types/patent.js.map +1 -0
- package/dist/types.d.ts +12 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +9 -4
- package/server.json +3 -3
- package/skills/unicli/SKILL.md +1 -1
- package/skills/unicli-claude-code/SKILL.md +1 -1
- package/skills/unicli-hermes/SKILL.md +1 -1
- package/src/adapters/bilibili/comments-tree.test.ts +41 -0
- package/src/adapters/bilibili/comments.ts +78 -4
- package/src/adapters/bilibili/compat.ts +5 -2
- package/src/adapters/bilibili/download.ts +7 -4
- package/src/adapters/bilibili/wbi.ts +6 -3
- package/src/adapters/brave/search.yaml +53 -0
- package/src/adapters/cipo/_shared.ts +98 -0
- package/src/adapters/cipo/get.ts +188 -0
- package/src/adapters/cipo/legal-status.ts +148 -0
- package/src/adapters/cipo/search.ts +195 -0
- package/src/adapters/cnipa/_shared.ts +138 -0
- package/src/adapters/cnipa/get.ts +199 -0
- package/src/adapters/cnipa/legal-status.ts +162 -0
- package/src/adapters/cnipa/search.ts +229 -0
- package/src/adapters/dpma/get.yaml +67 -0
- package/src/adapters/dpma/search.yaml +77 -0
- package/src/adapters/duckduckgo/search.yaml +54 -0
- package/src/adapters/duckduckgo/suggest.yaml +52 -0
- package/src/adapters/epo/family.yaml +69 -0
- package/src/adapters/epo/get.yaml +74 -0
- package/src/adapters/epo/legal-status.yaml +63 -0
- package/src/adapters/epo/search.yaml +84 -0
- package/src/adapters/espacenet/_shared.ts +98 -0
- package/src/adapters/espacenet/family.ts +161 -0
- package/src/adapters/espacenet/get.ts +185 -0
- package/src/adapters/espacenet/legal-status.ts +151 -0
- package/src/adapters/espacenet/search.ts +229 -0
- package/src/adapters/facebook/subtitles.ts +44 -0
- package/src/adapters/fips/_shared.ts +109 -0
- package/src/adapters/fips/get.ts +186 -0
- package/src/adapters/fips/search.ts +195 -0
- package/src/adapters/freepatentsonline-web/_shared.ts +273 -0
- package/src/adapters/freepatentsonline-web/get.ts +144 -0
- package/src/adapters/freepatentsonline-web/search.ts +170 -0
- package/src/adapters/google-patents-bq/prior-art.yaml +80 -0
- package/src/adapters/google-patents-bq/search.yaml +97 -0
- package/src/adapters/google-patents-web/_shared.ts +242 -0
- package/src/adapters/google-patents-web/get.ts +224 -0
- package/src/adapters/google-patents-web/search.ts +196 -0
- package/src/adapters/inpi-br/_shared.ts +98 -0
- package/src/adapters/inpi-br/get.ts +193 -0
- package/src/adapters/inpi-br/search.ts +206 -0
- package/src/adapters/inpi-fr/get.yaml +62 -0
- package/src/adapters/inpi-fr/search.yaml +74 -0
- package/src/adapters/instagram/subtitles.ts +44 -0
- package/src/adapters/ipaustralia/get.yaml +67 -0
- package/src/adapters/ipaustralia/search.yaml +74 -0
- package/src/adapters/jpo/get.yaml +63 -0
- package/src/adapters/jpo/search.yaml +76 -0
- package/src/adapters/kipris/get.yaml +69 -0
- package/src/adapters/kipris/legal-status.yaml +58 -0
- package/src/adapters/kipris/search.yaml +79 -0
- package/src/adapters/lens/get.yaml +64 -0
- package/src/adapters/lens/search.yaml +82 -0
- package/src/adapters/mastodon/statuses.test.ts +82 -0
- package/src/adapters/mastodon/statuses.ts +208 -0
- package/src/adapters/patsnap/get.yaml +65 -0
- package/src/adapters/patsnap/search.yaml +77 -0
- package/src/adapters/pqai/prior-art.yaml +59 -0
- package/src/adapters/pqai/search.yaml +60 -0
- package/src/adapters/reddit/comments-tree.test.ts +79 -0
- package/src/adapters/reddit/comments.ts +159 -0
- package/src/adapters/threads/post.test.ts +64 -0
- package/src/adapters/threads/post.ts +366 -0
- package/src/adapters/threads/user.yaml +73 -0
- package/src/adapters/tiktok/subtitles.ts +44 -0
- package/src/adapters/twitter/accept.ts +5 -2
- package/src/adapters/twitter/browser-fallback.ts +138 -0
- package/src/adapters/twitter/browser-state.ts +74 -0
- package/src/adapters/twitter/client.ts +51 -21
- package/src/adapters/twitter/reply-dm.ts +5 -2
- package/src/adapters/twitter/reply.ts +1 -0
- package/src/adapters/twitter/search.ts +12 -38
- package/src/adapters/twitter/thread.test.ts +43 -0
- package/src/adapters/twitter/thread.ts +44 -2
- package/src/adapters/twitter/trending.ts +14 -95
- package/src/adapters/ukipo/info.yaml +43 -0
- package/src/adapters/uspto/get.yaml +67 -0
- package/src/adapters/uspto/legal-status.yaml +58 -0
- package/src/adapters/uspto/search.yaml +88 -0
- package/src/adapters/wipo-patentscope/info.yaml +43 -0
- package/src/adapters/xiaohongshu/browser-state.ts +95 -0
- package/src/adapters/xiaohongshu/comments.ts +29 -6
- package/src/adapters/xiaohongshu/download.ts +60 -11
- package/src/adapters/xiaohongshu/search.ts +18 -6
- package/src/adapters/xiaohongshu/trending.ts +112 -0
- package/src/adapters/yahoo/search.yaml +52 -0
- package/src/adapters/youtube/comments-microformat.test.ts +35 -0
- package/src/adapters/youtube/comments-tree.test.ts +74 -0
- package/src/adapters/youtube/comments.ts +166 -12
- package/src/adapters/youtube/subtitles.ts +44 -0
- package/src/adapters/yt-dlp/subtitles.ts +43 -0
- package/src/adapters/zhihu/answer-detail.test.ts +83 -0
- package/src/adapters/zhihu/answer-detail.ts +275 -0
- package/src/adapters/zhihu/comment-tree.test.ts +57 -0
- package/src/adapters/zhihu/comment.ts +186 -0
- package/src/adapters/zhihu/recommend.test.ts +65 -0
- package/src/adapters/zhihu/recommend.ts +207 -0
|
@@ -0,0 +1,919 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @owner src::commands::patent
|
|
3
|
+
* @does Top-level `unicli patent` meta-command — fans out search / get / family / citations / legal-status / prior-art / doctor across every registered patent adapter (discovered via the `patent.*` capability convention), normalises into PatentRecord, dedupes by family, and emits the standard agent envelope.
|
|
4
|
+
* @needs src/registry.ts, src/types/patent.ts, src/engine/normalizer/patent-envelope.ts, src/engine/kernel/execute.ts, src/output/formatter.ts
|
|
5
|
+
* @feeds src/cli.ts (registerPatentCommand wiring)
|
|
6
|
+
* @breaks emits PATENT_INVALID_NUMBER envelope on unknown publication-number prefix; PATENT_NOT_FOUND when every fan-out source returns empty; never falls back to a non-patent adapter (rule 02)
|
|
7
|
+
* @invariants --sources default = uspto,epo,jpo; --sources all = every registered adapter whose `capabilities[]` carries any `patent.*` tag; routing by publication-number prefix uses an explicit jurisdiction → adapter table — no fuzzy matching
|
|
8
|
+
* @side-effects spawns adapter pipelines via engine kernel; reads env for adapter-specific API keys; writes to stdout/stderr only
|
|
9
|
+
* @perf O(N · M) where N = sources, M = result rows per source — fan-out is sequential today; can move to parallel once the engine kernel proves reentrant under load
|
|
10
|
+
* @concurrency safe — Commander handlers run one at a time per process
|
|
11
|
+
* @test tests/unit/commands/patent.test.ts
|
|
12
|
+
* @stability experimental — wave-1 surface; field names locked, behaviour evolves with adapters
|
|
13
|
+
* @since 2026-05-18
|
|
14
|
+
*
|
|
15
|
+
* Capability convention introduced here:
|
|
16
|
+
*
|
|
17
|
+
* patent.search — adapter exposes a free-text + filter search
|
|
18
|
+
* patent.get — adapter retrieves one record by publication-number
|
|
19
|
+
* patent.family — adapter returns DOCDB / INPADOC family members
|
|
20
|
+
* patent.citations — adapter returns citing / cited records
|
|
21
|
+
* patent.legal-status — adapter resolves prosecution / grant status
|
|
22
|
+
* patent.fulltext — adapter delivers description + claims text
|
|
23
|
+
* patent.prior-art — adapter performs semantic prior-art retrieval
|
|
24
|
+
*
|
|
25
|
+
* Each patent-vertical YAML adapter must include the relevant tags in its
|
|
26
|
+
* `capabilities:` array so this meta-command discovers it without
|
|
27
|
+
* hard-coding a site list.
|
|
28
|
+
*/
|
|
29
|
+
import { commandStrategy, getAllAdapters, resolveCommand, } from "../registry.js";
|
|
30
|
+
import { detectFormat, format } from "../output/formatter.js";
|
|
31
|
+
import { makeCtx } from "../output/envelope.js";
|
|
32
|
+
import { ExitCode, Strategy } from "../types.js";
|
|
33
|
+
import { buildInvocation, execute } from "../engine/kernel/execute.js";
|
|
34
|
+
import { resolveAdapterVerificationStatus, } from "./patent-doctor.js";
|
|
35
|
+
// ── Publication-number prefix table ─────────────────────────────────────
|
|
36
|
+
//
|
|
37
|
+
// ST.16 two-letter country codes → the canonical first-party adapter that
|
|
38
|
+
// resolves get/family for that jurisdiction. Espacenet (EPO) is the broker
|
|
39
|
+
// for `family` regardless of prefix, but for `get` we go to the home office
|
|
40
|
+
// when one is registered, then fall back to espacenet.
|
|
41
|
+
const JURISDICTION_ADAPTERS = {
|
|
42
|
+
US: "uspto",
|
|
43
|
+
EP: "epo",
|
|
44
|
+
WO: "epo", // PCT — EPO Espacenet brokers
|
|
45
|
+
JP: "jpo",
|
|
46
|
+
KR: "kipris",
|
|
47
|
+
CN: "cnipa",
|
|
48
|
+
DE: "dpma",
|
|
49
|
+
FR: "inpi-fr",
|
|
50
|
+
GB: "espacenet",
|
|
51
|
+
CA: "cipo",
|
|
52
|
+
AU: "ipaustralia",
|
|
53
|
+
BR: "inpi-br",
|
|
54
|
+
RU: "fips",
|
|
55
|
+
};
|
|
56
|
+
const FAMILY_BROKER = "epo";
|
|
57
|
+
const DEFAULT_SOURCES = ["uspto", "epo", "jpo"];
|
|
58
|
+
// Vertical capability tags (re-exported so adapters and tests can grep).
|
|
59
|
+
export const PATENT_CAPABILITIES = [
|
|
60
|
+
"patent.search",
|
|
61
|
+
"patent.get",
|
|
62
|
+
"patent.family",
|
|
63
|
+
"patent.citations",
|
|
64
|
+
"patent.legal-status",
|
|
65
|
+
"patent.fulltext",
|
|
66
|
+
"patent.prior-art",
|
|
67
|
+
];
|
|
68
|
+
// ── Discovery helpers ───────────────────────────────────────────────────
|
|
69
|
+
function hasAnyPatentCapability(adapter) {
|
|
70
|
+
for (const command of Object.values(adapter.commands)) {
|
|
71
|
+
const caps = command.capabilities ?? [];
|
|
72
|
+
if (caps.some((cap) => cap.startsWith("patent.")))
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Find every registered adapter whose command set carries any patent.* tag.
|
|
79
|
+
* Returns a stable alphabetical ordering so output is deterministic.
|
|
80
|
+
*/
|
|
81
|
+
export function listPatentAdapters() {
|
|
82
|
+
return getAllAdapters()
|
|
83
|
+
.filter(hasAnyPatentCapability)
|
|
84
|
+
.sort((a, b) => a.name.localeCompare(b.name));
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Filter the list of adapter names by a `--sources` string. `all` expands to
|
|
88
|
+
* every patent-capable adapter discovered in the registry.
|
|
89
|
+
*/
|
|
90
|
+
export function resolveSources(sourcesArg, fallback = DEFAULT_SOURCES) {
|
|
91
|
+
if (!sourcesArg || sourcesArg.trim().length === 0) {
|
|
92
|
+
return [...fallback];
|
|
93
|
+
}
|
|
94
|
+
if (sourcesArg.trim() === "all") {
|
|
95
|
+
return listPatentAdapters().map((a) => a.name);
|
|
96
|
+
}
|
|
97
|
+
return sourcesArg
|
|
98
|
+
.split(",")
|
|
99
|
+
.map((s) => s.trim())
|
|
100
|
+
.filter((s) => s.length > 0);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Map a publication number to the adapter that should serve `get`/`family`
|
|
104
|
+
* for it. Returns `undefined` when the prefix is not recognised — callers
|
|
105
|
+
* surface PATENT_INVALID_NUMBER in that case.
|
|
106
|
+
*/
|
|
107
|
+
export function routeByPublicationPrefix(publicationNumber) {
|
|
108
|
+
const match = /^([A-Z]{2})/.exec(publicationNumber.trim().toUpperCase());
|
|
109
|
+
if (!match)
|
|
110
|
+
return undefined;
|
|
111
|
+
return JURISDICTION_ADAPTERS[match[1]];
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Find the first command on `adapter` that carries the requested patent.*
|
|
115
|
+
* capability tag. Adapters may name their command anything; the capability
|
|
116
|
+
* tag is what wires them up.
|
|
117
|
+
*/
|
|
118
|
+
export function findCommandByCapability(adapter, capability) {
|
|
119
|
+
for (const [name, command] of Object.entries(adapter.commands)) {
|
|
120
|
+
const caps = command.capabilities ?? [];
|
|
121
|
+
if (caps.includes(capability))
|
|
122
|
+
return { name, command };
|
|
123
|
+
}
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
// ── Reciprocal-rank fusion ──────────────────────────────────────────────
|
|
127
|
+
/**
|
|
128
|
+
* Reciprocal-rank fusion across ranked result lists. The scoring follows
|
|
129
|
+
* Cormack/Clarke/Buettcher 2009 (`score = Σ 1 / (k + rank)` with k = 60).
|
|
130
|
+
* Records are keyed by `family_id` when present, falling back to
|
|
131
|
+
* canonical publication_number — that is the dedupe axis required by the
|
|
132
|
+
* patent vertical (different offices issue separate publication numbers
|
|
133
|
+
* for the same invention).
|
|
134
|
+
*/
|
|
135
|
+
export function reciprocalRankFusion(rankedLists, options = {}) {
|
|
136
|
+
const k = options.k ?? 60;
|
|
137
|
+
const buckets = new Map();
|
|
138
|
+
let order = 0;
|
|
139
|
+
for (const list of rankedLists) {
|
|
140
|
+
for (let rank = 0; rank < list.length; rank++) {
|
|
141
|
+
const record = list[rank];
|
|
142
|
+
const key = record.family_id ?? record.publication_number;
|
|
143
|
+
const existing = buckets.get(key);
|
|
144
|
+
const increment = 1 / (k + rank + 1);
|
|
145
|
+
if (existing) {
|
|
146
|
+
existing.score += increment;
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
buckets.set(key, { score: increment, record, firstSeen: order++ });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
const fused = [...buckets.values()].sort((a, b) => b.score - a.score || a.firstSeen - b.firstSeen);
|
|
154
|
+
const top = options.topN ? fused.slice(0, options.topN) : fused;
|
|
155
|
+
return top.map((b) => b.record);
|
|
156
|
+
}
|
|
157
|
+
// ── Output formatting (search / get / prior-art) ───────────────────────
|
|
158
|
+
/**
|
|
159
|
+
* Column sets exposed to `--detailed`. The default set mirrors the wave-1
|
|
160
|
+
* surface (publication_number / title / publication_date / source_adapter);
|
|
161
|
+
* the detailed set adds inventors / assignees / classifications / dates
|
|
162
|
+
* and uses a per-record block in markdown rather than a 4-column table.
|
|
163
|
+
*/
|
|
164
|
+
const DEFAULT_SEARCH_COLUMNS = [
|
|
165
|
+
"publication_number",
|
|
166
|
+
"title",
|
|
167
|
+
"publication_date",
|
|
168
|
+
"source_adapter",
|
|
169
|
+
];
|
|
170
|
+
const DETAILED_SEARCH_COLUMNS = [
|
|
171
|
+
"publication_number",
|
|
172
|
+
"title",
|
|
173
|
+
"abstract",
|
|
174
|
+
"inventors",
|
|
175
|
+
"assignees",
|
|
176
|
+
"classifications",
|
|
177
|
+
"filing_date",
|
|
178
|
+
"publication_date",
|
|
179
|
+
"grant_date",
|
|
180
|
+
"priority_date",
|
|
181
|
+
"kind_code",
|
|
182
|
+
"legal_status",
|
|
183
|
+
"family_id",
|
|
184
|
+
"cited_by_count",
|
|
185
|
+
"cites_count",
|
|
186
|
+
"claims_count",
|
|
187
|
+
"relevance_score",
|
|
188
|
+
"source_adapter",
|
|
189
|
+
"source_url",
|
|
190
|
+
];
|
|
191
|
+
/**
|
|
192
|
+
* Strip the raw field from records when `--include-raw` is not set.
|
|
193
|
+
* Preserves the input shape otherwise. Uses structuredClone to avoid
|
|
194
|
+
* mutating callers' arrays.
|
|
195
|
+
*/
|
|
196
|
+
function stripRaw(records) {
|
|
197
|
+
return records.map((r) => {
|
|
198
|
+
if (r.raw === undefined)
|
|
199
|
+
return r;
|
|
200
|
+
const { raw: _raw, ...rest } = r;
|
|
201
|
+
void _raw;
|
|
202
|
+
return rest;
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
/**
|
|
206
|
+
* Serialise a single PatentRecord as one CSV row given a column list. The
|
|
207
|
+
* CSV serializer escapes commas, double-quotes, and newlines per RFC 4180.
|
|
208
|
+
* Array fields are JSON-serialized inside the cell so the cell remains a
|
|
209
|
+
* single column.
|
|
210
|
+
*/
|
|
211
|
+
function csvCell(value) {
|
|
212
|
+
if (value === undefined || value === null)
|
|
213
|
+
return "";
|
|
214
|
+
const serialized = Array.isArray(value) || typeof value === "object"
|
|
215
|
+
? JSON.stringify(value)
|
|
216
|
+
: String(value);
|
|
217
|
+
if (serialized.includes(",") ||
|
|
218
|
+
serialized.includes('"') ||
|
|
219
|
+
serialized.includes("\n") ||
|
|
220
|
+
serialized.includes("\r")) {
|
|
221
|
+
return `"${serialized.replace(/"/g, '""')}"`;
|
|
222
|
+
}
|
|
223
|
+
return serialized;
|
|
224
|
+
}
|
|
225
|
+
function renderCsv(records, columns) {
|
|
226
|
+
const header = columns.join(",");
|
|
227
|
+
const rows = records.map((r) => columns
|
|
228
|
+
.map((c) => csvCell(r[c]))
|
|
229
|
+
.join(","));
|
|
230
|
+
return [header, ...rows].join("\n");
|
|
231
|
+
}
|
|
232
|
+
function renderJsonl(records) {
|
|
233
|
+
return records.map((r) => JSON.stringify(r)).join("\n");
|
|
234
|
+
}
|
|
235
|
+
function renderDetailedMarkdown(records) {
|
|
236
|
+
// One block per record. Skips undefined fields so the output is dense.
|
|
237
|
+
// The block separator is a horizontal rule; that keeps copy-paste into
|
|
238
|
+
// an agent prompt deterministic.
|
|
239
|
+
const blocks = [];
|
|
240
|
+
for (const r of records) {
|
|
241
|
+
const lines = [];
|
|
242
|
+
lines.push(`### ${r.publication_number}`);
|
|
243
|
+
if (r.title)
|
|
244
|
+
lines.push(`**title**: ${r.title}`);
|
|
245
|
+
if (r.abstract)
|
|
246
|
+
lines.push(`**abstract**: ${r.abstract}`);
|
|
247
|
+
if (r.inventors && r.inventors.length > 0)
|
|
248
|
+
lines.push(`**inventors**: ${r.inventors.map((p) => p.name).join("; ")}`);
|
|
249
|
+
if (r.assignees && r.assignees.length > 0)
|
|
250
|
+
lines.push(`**assignees**: ${r.assignees.map((p) => p.name).join("; ")}`);
|
|
251
|
+
if (r.classifications && r.classifications.length > 0)
|
|
252
|
+
lines.push(`**classifications**: ${r.classifications
|
|
253
|
+
.map((c) => `${c.scheme.toUpperCase()}:${c.code}`)
|
|
254
|
+
.join(", ")}`);
|
|
255
|
+
if (r.filing_date)
|
|
256
|
+
lines.push(`**filing_date**: ${r.filing_date}`);
|
|
257
|
+
if (r.publication_date)
|
|
258
|
+
lines.push(`**publication_date**: ${r.publication_date}`);
|
|
259
|
+
if (r.grant_date)
|
|
260
|
+
lines.push(`**grant_date**: ${r.grant_date}`);
|
|
261
|
+
if (r.priority_date)
|
|
262
|
+
lines.push(`**priority_date**: ${r.priority_date}`);
|
|
263
|
+
if (r.kind_code)
|
|
264
|
+
lines.push(`**kind_code**: ${r.kind_code}`);
|
|
265
|
+
if (r.legal_status)
|
|
266
|
+
lines.push(`**legal_status**: ${r.legal_status}`);
|
|
267
|
+
if (r.family_id)
|
|
268
|
+
lines.push(`**family_id**: ${r.family_id}`);
|
|
269
|
+
if (r.cited_by_count !== undefined)
|
|
270
|
+
lines.push(`**cited_by_count**: ${r.cited_by_count}`);
|
|
271
|
+
if (r.cites_count !== undefined)
|
|
272
|
+
lines.push(`**cites_count**: ${r.cites_count}`);
|
|
273
|
+
if (r.claims_count !== undefined)
|
|
274
|
+
lines.push(`**claims_count**: ${r.claims_count}`);
|
|
275
|
+
if (r.relevance_score !== undefined)
|
|
276
|
+
lines.push(`**relevance_score**: ${r.relevance_score}`);
|
|
277
|
+
lines.push(`**source_adapter**: ${r.source_adapter}`);
|
|
278
|
+
if (r.source_url)
|
|
279
|
+
lines.push(`**source_url**: ${r.source_url}`);
|
|
280
|
+
blocks.push(lines.join("\n"));
|
|
281
|
+
}
|
|
282
|
+
return blocks.join("\n\n---\n\n");
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Resolve the output payload for the patent search / get / prior-art
|
|
286
|
+
* commands honouring `--detailed`, `--include-raw`, and `--format jsonl`
|
|
287
|
+
* (alongside json / md / csv). Returns either a rendered string (when the
|
|
288
|
+
* format is a patent-vertical-specific output like jsonl / csv / detailed
|
|
289
|
+
* md) or `null` to signal the caller should fall through to the standard
|
|
290
|
+
* envelope formatter.
|
|
291
|
+
*/
|
|
292
|
+
function renderPatentOutput(records, opts) {
|
|
293
|
+
const cleaned = opts.includeRaw ? records : stripRaw(records);
|
|
294
|
+
const fmt = (opts.format ?? "").toLowerCase();
|
|
295
|
+
if (fmt === "jsonl") {
|
|
296
|
+
return { output: renderJsonl(cleaned), standardFormatter: false };
|
|
297
|
+
}
|
|
298
|
+
if (fmt === "csv") {
|
|
299
|
+
const columns = opts.detailed
|
|
300
|
+
? DETAILED_SEARCH_COLUMNS
|
|
301
|
+
: DEFAULT_SEARCH_COLUMNS;
|
|
302
|
+
return { output: renderCsv(cleaned, columns), standardFormatter: false };
|
|
303
|
+
}
|
|
304
|
+
if (opts.detailed && (fmt === "" || fmt === "md")) {
|
|
305
|
+
return {
|
|
306
|
+
output: renderDetailedMarkdown(cleaned),
|
|
307
|
+
standardFormatter: false,
|
|
308
|
+
};
|
|
309
|
+
}
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
// ── Pipeline-result coercion ────────────────────────────────────────────
|
|
313
|
+
/**
|
|
314
|
+
* Coerce a YAML-emitted value that is expected to be a JSON-serialized array
|
|
315
|
+
* (the `| json` filter result). Returns `undefined` when the value is not a
|
|
316
|
+
* non-empty array after parsing — never an empty array (that would surface as
|
|
317
|
+
* "the upstream said zero inventors" when it actually said "no field").
|
|
318
|
+
*/
|
|
319
|
+
function parseJsonArray(value) {
|
|
320
|
+
if (Array.isArray(value)) {
|
|
321
|
+
return value.length > 0 ? value : undefined;
|
|
322
|
+
}
|
|
323
|
+
if (typeof value !== "string" || value.length === 0)
|
|
324
|
+
return undefined;
|
|
325
|
+
// Tolerate authors using `| json` on a missing field — the filter yields
|
|
326
|
+
// the literal string "undefined" in that case.
|
|
327
|
+
if (value === "undefined" || value === "null")
|
|
328
|
+
return undefined;
|
|
329
|
+
try {
|
|
330
|
+
const parsed = JSON.parse(value);
|
|
331
|
+
if (!Array.isArray(parsed) || parsed.length === 0)
|
|
332
|
+
return undefined;
|
|
333
|
+
return parsed;
|
|
334
|
+
}
|
|
335
|
+
catch {
|
|
336
|
+
return undefined;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
/**
|
|
340
|
+
* Coerce a YAML-emitted scalar into a finite number. The pipeline template
|
|
341
|
+
* engine stringifies every map output, so `42` arrives as `"42"`. Returns
|
|
342
|
+
* `undefined` when the input is not a finite number — never a NaN, never
|
|
343
|
+
* a silent zero (that would manufacture a count that did not exist
|
|
344
|
+
* upstream).
|
|
345
|
+
*/
|
|
346
|
+
function asFiniteNumber(value) {
|
|
347
|
+
if (typeof value === "number") {
|
|
348
|
+
return Number.isFinite(value) ? value : undefined;
|
|
349
|
+
}
|
|
350
|
+
if (typeof value === "string") {
|
|
351
|
+
if (value.length === 0)
|
|
352
|
+
return undefined;
|
|
353
|
+
const n = Number(value);
|
|
354
|
+
return Number.isFinite(n) ? n : undefined;
|
|
355
|
+
}
|
|
356
|
+
return undefined;
|
|
357
|
+
}
|
|
358
|
+
function coerceToPatentRecords(rows, source) {
|
|
359
|
+
if (!Array.isArray(rows))
|
|
360
|
+
return [];
|
|
361
|
+
const out = [];
|
|
362
|
+
for (const row of rows) {
|
|
363
|
+
if (!row || typeof row !== "object")
|
|
364
|
+
continue;
|
|
365
|
+
const r = row;
|
|
366
|
+
if (typeof r.publication_number !== "string")
|
|
367
|
+
continue;
|
|
368
|
+
if (r.publication_number.length === 0)
|
|
369
|
+
continue;
|
|
370
|
+
const record = {
|
|
371
|
+
publication_number: r.publication_number,
|
|
372
|
+
source_adapter: typeof r.source_adapter === "string" ? r.source_adapter : source,
|
|
373
|
+
retrieved_at: typeof r.retrieved_at === "string" && r.retrieved_at.length > 0
|
|
374
|
+
? r.retrieved_at
|
|
375
|
+
: new Date().toISOString(),
|
|
376
|
+
};
|
|
377
|
+
if (typeof r.application_number === "string")
|
|
378
|
+
record.application_number = r.application_number;
|
|
379
|
+
if (typeof r.kind_code === "string")
|
|
380
|
+
record.kind_code = r.kind_code;
|
|
381
|
+
if (typeof r.title === "string")
|
|
382
|
+
record.title = r.title;
|
|
383
|
+
if (typeof r.abstract === "string")
|
|
384
|
+
record.abstract = r.abstract;
|
|
385
|
+
if (typeof r.snippet === "string")
|
|
386
|
+
record.snippet = r.snippet;
|
|
387
|
+
const inventors = parseJsonArray(r.inventors);
|
|
388
|
+
if (inventors)
|
|
389
|
+
record.inventors = inventors;
|
|
390
|
+
const assignees = parseJsonArray(r.assignees);
|
|
391
|
+
if (assignees)
|
|
392
|
+
record.assignees = assignees;
|
|
393
|
+
if (typeof r.filing_date === "string")
|
|
394
|
+
record.filing_date = r.filing_date;
|
|
395
|
+
if (typeof r.publication_date === "string")
|
|
396
|
+
record.publication_date = r.publication_date;
|
|
397
|
+
if (typeof r.grant_date === "string")
|
|
398
|
+
record.grant_date = r.grant_date;
|
|
399
|
+
if (typeof r.priority_date === "string")
|
|
400
|
+
record.priority_date = r.priority_date;
|
|
401
|
+
const classifications = parseJsonArray(r.classifications);
|
|
402
|
+
if (classifications)
|
|
403
|
+
record.classifications =
|
|
404
|
+
classifications;
|
|
405
|
+
if (typeof r.family_id === "string")
|
|
406
|
+
record.family_id = r.family_id;
|
|
407
|
+
const familyMembers = parseJsonArray(r.family_members);
|
|
408
|
+
if (familyMembers)
|
|
409
|
+
record.family_members = familyMembers;
|
|
410
|
+
if (typeof r.legal_status === "string")
|
|
411
|
+
record.legal_status = r.legal_status;
|
|
412
|
+
const claimsCount = asFiniteNumber(r.claims_count);
|
|
413
|
+
if (claimsCount !== undefined)
|
|
414
|
+
record.claims_count = claimsCount;
|
|
415
|
+
const citedByCount = asFiniteNumber(r.cited_by_count);
|
|
416
|
+
if (citedByCount !== undefined)
|
|
417
|
+
record.cited_by_count = citedByCount;
|
|
418
|
+
const citesCount = asFiniteNumber(r.cites_count);
|
|
419
|
+
if (citesCount !== undefined)
|
|
420
|
+
record.cites_count = citesCount;
|
|
421
|
+
if (typeof r.pdf_url === "string" && r.pdf_url.length > 0)
|
|
422
|
+
record.pdf_url = r.pdf_url;
|
|
423
|
+
const relevanceScore = asFiniteNumber(r.relevance_score);
|
|
424
|
+
if (relevanceScore !== undefined)
|
|
425
|
+
record.relevance_score = relevanceScore;
|
|
426
|
+
if (typeof r.source_url === "string")
|
|
427
|
+
record.source_url = r.source_url;
|
|
428
|
+
if (r.raw !== undefined)
|
|
429
|
+
record.raw = r.raw;
|
|
430
|
+
out.push(record);
|
|
431
|
+
}
|
|
432
|
+
return out;
|
|
433
|
+
}
|
|
434
|
+
async function runAdapterCommand(source, capability, args) {
|
|
435
|
+
const adapter = getAllAdapters().find((a) => a.name === source);
|
|
436
|
+
if (!adapter) {
|
|
437
|
+
return {
|
|
438
|
+
source,
|
|
439
|
+
records: [],
|
|
440
|
+
error: {
|
|
441
|
+
code: "adapter_not_found",
|
|
442
|
+
message: `unknown source: ${source}`,
|
|
443
|
+
},
|
|
444
|
+
};
|
|
445
|
+
}
|
|
446
|
+
const found = findCommandByCapability(adapter, capability);
|
|
447
|
+
if (!found) {
|
|
448
|
+
return {
|
|
449
|
+
source,
|
|
450
|
+
records: [],
|
|
451
|
+
error: {
|
|
452
|
+
code: "capability_unsupported",
|
|
453
|
+
message: `${source} does not expose ${capability}`,
|
|
454
|
+
},
|
|
455
|
+
};
|
|
456
|
+
}
|
|
457
|
+
const inv = buildInvocation("cli", source, found.name, { args, source: "internal" }, { approved: true });
|
|
458
|
+
if (!inv) {
|
|
459
|
+
return {
|
|
460
|
+
source,
|
|
461
|
+
records: [],
|
|
462
|
+
error: {
|
|
463
|
+
code: "build_invocation_failed",
|
|
464
|
+
message: `could not build invocation for ${source}.${found.name}`,
|
|
465
|
+
},
|
|
466
|
+
};
|
|
467
|
+
}
|
|
468
|
+
const result = await execute(inv);
|
|
469
|
+
if (result.error) {
|
|
470
|
+
return {
|
|
471
|
+
source,
|
|
472
|
+
records: [],
|
|
473
|
+
error: {
|
|
474
|
+
code: result.error.code ?? "execution_error",
|
|
475
|
+
message: result.error.message ?? "adapter pipeline failed",
|
|
476
|
+
},
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
return {
|
|
480
|
+
source,
|
|
481
|
+
records: coerceToPatentRecords(result.results, source),
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
async function runSearch(program, query, opts) {
|
|
485
|
+
const startedAt = Date.now();
|
|
486
|
+
// Resolve format precedence: explicit `--format` on the patent subcommand
|
|
487
|
+
// wins over the global `-f` (it can name `jsonl`, which the global formatter
|
|
488
|
+
// does not understand).
|
|
489
|
+
const requestedFormat = opts.format ?? program.opts().format;
|
|
490
|
+
const fmt = detectFormat(requestedFormat === "jsonl"
|
|
491
|
+
? "json"
|
|
492
|
+
: requestedFormat);
|
|
493
|
+
const ctx = makeCtx("patent.search", startedAt);
|
|
494
|
+
const sources = resolveSources(opts.sources);
|
|
495
|
+
const limit = Number(opts.limit ?? "20");
|
|
496
|
+
const args = { query, limit };
|
|
497
|
+
if (opts.since)
|
|
498
|
+
args.date_from = `${opts.since}-01-01`;
|
|
499
|
+
if (opts.cpc)
|
|
500
|
+
args.cpc = opts.cpc;
|
|
501
|
+
const outcomes = [];
|
|
502
|
+
for (const source of sources) {
|
|
503
|
+
outcomes.push(await runAdapterCommand(source, "patent.search", args));
|
|
504
|
+
}
|
|
505
|
+
const lists = outcomes.map((o) => o.records);
|
|
506
|
+
const fused = reciprocalRankFusion(lists, { topN: limit });
|
|
507
|
+
const errors = outcomes.filter((o) => o.error);
|
|
508
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
509
|
+
ctx.surface = "web";
|
|
510
|
+
if (fused.length === 0) {
|
|
511
|
+
ctx.error = {
|
|
512
|
+
code: "PATENT_NOT_FOUND",
|
|
513
|
+
message: `no patent records returned for "${query}" across [${sources.join(", ")}]`,
|
|
514
|
+
suggestion: errors.length > 0
|
|
515
|
+
? `Per-source errors: ${errors.map((e) => `${e.source}: ${e.error?.code}`).join("; ")}`
|
|
516
|
+
: "Try --sources all or relax filters (--since / --cpc).",
|
|
517
|
+
retryable: errors.some((e) => e.error?.code === "rate_limit"),
|
|
518
|
+
};
|
|
519
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
520
|
+
process.exit(ExitCode.EMPTY_RESULT);
|
|
521
|
+
}
|
|
522
|
+
const patentOutput = renderPatentOutput(fused, {
|
|
523
|
+
detailed: opts.detailed,
|
|
524
|
+
format: requestedFormat,
|
|
525
|
+
includeRaw: opts.includeRaw,
|
|
526
|
+
});
|
|
527
|
+
if (patentOutput) {
|
|
528
|
+
console.log(patentOutput.output);
|
|
529
|
+
return;
|
|
530
|
+
}
|
|
531
|
+
const columns = opts.detailed
|
|
532
|
+
? [...DETAILED_SEARCH_COLUMNS]
|
|
533
|
+
: [...DEFAULT_SEARCH_COLUMNS];
|
|
534
|
+
const records = opts.includeRaw ? fused : stripRaw(fused);
|
|
535
|
+
console.log(format(records, columns, fmt, ctx));
|
|
536
|
+
}
|
|
537
|
+
async function runGet(program, publicationNumber, opts = {}) {
|
|
538
|
+
const startedAt = Date.now();
|
|
539
|
+
const requestedFormat = opts.format ?? program.opts().format;
|
|
540
|
+
const fmt = detectFormat(requestedFormat === "jsonl"
|
|
541
|
+
? "json"
|
|
542
|
+
: requestedFormat);
|
|
543
|
+
const ctx = makeCtx("patent.get", startedAt);
|
|
544
|
+
const source = routeByPublicationPrefix(publicationNumber);
|
|
545
|
+
if (!source) {
|
|
546
|
+
ctx.error = {
|
|
547
|
+
code: "PATENT_INVALID_NUMBER",
|
|
548
|
+
message: `publication number "${publicationNumber}" has no recognised ST.16 jurisdiction prefix`,
|
|
549
|
+
suggestion: "Use a publication number with a two-letter country code prefix (US, EP, JP, KR, CN, DE, FR, GB, CA, AU, BR, RU).",
|
|
550
|
+
retryable: false,
|
|
551
|
+
};
|
|
552
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
553
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
554
|
+
process.exit(ExitCode.USAGE_ERROR);
|
|
555
|
+
}
|
|
556
|
+
const outcome = await runAdapterCommand(source, "patent.get", {
|
|
557
|
+
publication_number: publicationNumber,
|
|
558
|
+
});
|
|
559
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
560
|
+
ctx.surface = "web";
|
|
561
|
+
if (outcome.error || outcome.records.length === 0) {
|
|
562
|
+
ctx.error = {
|
|
563
|
+
code: outcome.error ? "PATENT_NOT_FOUND" : "PATENT_NOT_FOUND",
|
|
564
|
+
message: outcome.error?.message ??
|
|
565
|
+
`${source} returned no record for ${publicationNumber}`,
|
|
566
|
+
suggestion: `Verify the number on ${source}'s primary search, or retry against --sources all.`,
|
|
567
|
+
retryable: false,
|
|
568
|
+
};
|
|
569
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
570
|
+
process.exit(ExitCode.EMPTY_RESULT);
|
|
571
|
+
}
|
|
572
|
+
const patentOutput = renderPatentOutput(outcome.records, {
|
|
573
|
+
detailed: opts.detailed,
|
|
574
|
+
format: requestedFormat,
|
|
575
|
+
includeRaw: opts.includeRaw,
|
|
576
|
+
});
|
|
577
|
+
if (patentOutput) {
|
|
578
|
+
console.log(patentOutput.output);
|
|
579
|
+
return;
|
|
580
|
+
}
|
|
581
|
+
const records = opts.includeRaw ? outcome.records : stripRaw(outcome.records);
|
|
582
|
+
console.log(format(records, undefined, fmt, ctx));
|
|
583
|
+
}
|
|
584
|
+
async function runFamily(program, publicationNumber) {
|
|
585
|
+
const startedAt = Date.now();
|
|
586
|
+
const fmt = detectFormat(program.opts().format);
|
|
587
|
+
const ctx = makeCtx("patent.family", startedAt);
|
|
588
|
+
if (!routeByPublicationPrefix(publicationNumber)) {
|
|
589
|
+
ctx.error = {
|
|
590
|
+
code: "PATENT_INVALID_NUMBER",
|
|
591
|
+
message: `publication number "${publicationNumber}" has no recognised ST.16 jurisdiction prefix`,
|
|
592
|
+
suggestion: "Family lookups need a CC-prefixed publication number so we know which jurisdiction issued it.",
|
|
593
|
+
retryable: false,
|
|
594
|
+
};
|
|
595
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
596
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
597
|
+
process.exit(ExitCode.USAGE_ERROR);
|
|
598
|
+
}
|
|
599
|
+
// EPO is the canonical INPADOC family broker — try it first.
|
|
600
|
+
const primary = await runAdapterCommand(FAMILY_BROKER, "patent.family", {
|
|
601
|
+
publication_number: publicationNumber,
|
|
602
|
+
});
|
|
603
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
604
|
+
ctx.surface = "web";
|
|
605
|
+
if (primary.error || primary.records.length === 0) {
|
|
606
|
+
const fallback = await runAdapterCommand(routeByPublicationPrefix(publicationNumber), "patent.family", { publication_number: publicationNumber });
|
|
607
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
608
|
+
if (fallback.error || fallback.records.length === 0) {
|
|
609
|
+
ctx.error = {
|
|
610
|
+
code: "PATENT_FAMILY_BROKER_DOWN",
|
|
611
|
+
message: `EPO Espacenet family lookup failed and home-office fallback returned empty for ${publicationNumber}`,
|
|
612
|
+
suggestion: "Retry with --sources all or check unicli patent doctor.",
|
|
613
|
+
retryable: true,
|
|
614
|
+
};
|
|
615
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
616
|
+
process.exit(ExitCode.SERVICE_UNAVAILABLE);
|
|
617
|
+
}
|
|
618
|
+
console.log(format(fallback.records, undefined, fmt, ctx));
|
|
619
|
+
return;
|
|
620
|
+
}
|
|
621
|
+
console.log(format(primary.records, undefined, fmt, ctx));
|
|
622
|
+
}
|
|
623
|
+
async function runCitations(program, publicationNumber, opts) {
|
|
624
|
+
const startedAt = Date.now();
|
|
625
|
+
const fmt = detectFormat(program.opts().format);
|
|
626
|
+
const ctx = makeCtx("patent.citations", startedAt);
|
|
627
|
+
const source = routeByPublicationPrefix(publicationNumber);
|
|
628
|
+
if (!source) {
|
|
629
|
+
ctx.error = {
|
|
630
|
+
code: "PATENT_INVALID_NUMBER",
|
|
631
|
+
message: `publication number "${publicationNumber}" has no recognised ST.16 jurisdiction prefix`,
|
|
632
|
+
suggestion: "Provide a CC-prefixed publication number.",
|
|
633
|
+
retryable: false,
|
|
634
|
+
};
|
|
635
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
636
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
637
|
+
process.exit(ExitCode.USAGE_ERROR);
|
|
638
|
+
}
|
|
639
|
+
const direction = opts.direction ?? "citing";
|
|
640
|
+
const outcome = await runAdapterCommand(source, "patent.citations", {
|
|
641
|
+
publication_number: publicationNumber,
|
|
642
|
+
direction,
|
|
643
|
+
});
|
|
644
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
645
|
+
ctx.surface = "web";
|
|
646
|
+
if (outcome.error) {
|
|
647
|
+
ctx.error = {
|
|
648
|
+
code: "PATENT_NOT_FOUND",
|
|
649
|
+
message: outcome.error.message,
|
|
650
|
+
suggestion: `Verify ${publicationNumber} exists at ${source}, then retry.`,
|
|
651
|
+
retryable: false,
|
|
652
|
+
};
|
|
653
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
654
|
+
process.exit(ExitCode.EMPTY_RESULT);
|
|
655
|
+
}
|
|
656
|
+
console.log(format(outcome.records, undefined, fmt, ctx));
|
|
657
|
+
}
|
|
658
|
+
async function runLegalStatus(program, publicationNumbers) {
|
|
659
|
+
const startedAt = Date.now();
|
|
660
|
+
const fmt = detectFormat(program.opts().format);
|
|
661
|
+
const ctx = makeCtx("patent.legal-status", startedAt);
|
|
662
|
+
const invalid = publicationNumbers.filter((n) => !routeByPublicationPrefix(n));
|
|
663
|
+
if (invalid.length > 0) {
|
|
664
|
+
ctx.error = {
|
|
665
|
+
code: "PATENT_INVALID_NUMBER",
|
|
666
|
+
message: `unrecognised jurisdiction prefix on: ${invalid.join(", ")}`,
|
|
667
|
+
suggestion: "Every number must carry a two-letter ST.16 country code.",
|
|
668
|
+
retryable: false,
|
|
669
|
+
};
|
|
670
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
671
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
672
|
+
process.exit(ExitCode.USAGE_ERROR);
|
|
673
|
+
}
|
|
674
|
+
const byJurisdiction = new Map();
|
|
675
|
+
for (const number of publicationNumbers) {
|
|
676
|
+
const source = routeByPublicationPrefix(number);
|
|
677
|
+
const bucket = byJurisdiction.get(source) ?? [];
|
|
678
|
+
bucket.push(number);
|
|
679
|
+
byJurisdiction.set(source, bucket);
|
|
680
|
+
}
|
|
681
|
+
const all = [];
|
|
682
|
+
for (const [source, numbers] of byJurisdiction.entries()) {
|
|
683
|
+
const outcome = await runAdapterCommand(source, "patent.legal-status", {
|
|
684
|
+
publication_numbers: numbers,
|
|
685
|
+
});
|
|
686
|
+
all.push(...outcome.records);
|
|
687
|
+
}
|
|
688
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
689
|
+
ctx.surface = "web";
|
|
690
|
+
console.log(format(all, ["publication_number", "legal_status", "source_adapter"], fmt, ctx));
|
|
691
|
+
}
|
|
692
|
+
async function runPriorArt(program, opts) {
|
|
693
|
+
const startedAt = Date.now();
|
|
694
|
+
const requestedFormat = opts.format ?? program.opts().format;
|
|
695
|
+
const fmt = detectFormat(requestedFormat === "jsonl"
|
|
696
|
+
? "json"
|
|
697
|
+
: requestedFormat);
|
|
698
|
+
const ctx = makeCtx("patent.prior-art", startedAt);
|
|
699
|
+
if (!opts.abstract) {
|
|
700
|
+
ctx.error = {
|
|
701
|
+
code: "invalid_input",
|
|
702
|
+
message: "patent prior-art requires --abstract <text>",
|
|
703
|
+
suggestion: "Pass the candidate abstract as a single string: --abstract 'A method for …'.",
|
|
704
|
+
retryable: false,
|
|
705
|
+
};
|
|
706
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
707
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
708
|
+
process.exit(ExitCode.USAGE_ERROR);
|
|
709
|
+
}
|
|
710
|
+
const top = Number(opts.top ?? "20");
|
|
711
|
+
const sources = resolveSources(opts.sources, [
|
|
712
|
+
"pqai",
|
|
713
|
+
"google-patents-bq",
|
|
714
|
+
"epo",
|
|
715
|
+
]);
|
|
716
|
+
const outcomes = [];
|
|
717
|
+
for (const source of sources) {
|
|
718
|
+
outcomes.push(await runAdapterCommand(source, "patent.prior-art", {
|
|
719
|
+
abstract: opts.abstract,
|
|
720
|
+
limit: top,
|
|
721
|
+
}));
|
|
722
|
+
}
|
|
723
|
+
const lists = outcomes.map((o) => o.records);
|
|
724
|
+
const fused = reciprocalRankFusion(lists, { topN: top });
|
|
725
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
726
|
+
ctx.surface = "web";
|
|
727
|
+
if (fused.length === 0) {
|
|
728
|
+
const errors = outcomes.filter((o) => o.error);
|
|
729
|
+
ctx.error = {
|
|
730
|
+
code: "PATENT_NOT_FOUND",
|
|
731
|
+
message: `no prior-art candidates returned across [${sources.join(", ")}]`,
|
|
732
|
+
suggestion: errors.length > 0
|
|
733
|
+
? `Per-source errors: ${errors.map((e) => `${e.source}: ${e.error?.code}`).join("; ")}`
|
|
734
|
+
: "Try widening --sources, or supply more of the abstract.",
|
|
735
|
+
retryable: false,
|
|
736
|
+
};
|
|
737
|
+
console.error(format(null, undefined, fmt, ctx));
|
|
738
|
+
process.exit(ExitCode.EMPTY_RESULT);
|
|
739
|
+
}
|
|
740
|
+
const patentOutput = renderPatentOutput(fused, {
|
|
741
|
+
detailed: opts.detailed,
|
|
742
|
+
format: requestedFormat,
|
|
743
|
+
includeRaw: opts.includeRaw,
|
|
744
|
+
});
|
|
745
|
+
if (patentOutput) {
|
|
746
|
+
console.log(patentOutput.output);
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
const columns = opts.detailed
|
|
750
|
+
? [...DETAILED_SEARCH_COLUMNS]
|
|
751
|
+
: ["publication_number", "title", "abstract", "source_adapter"];
|
|
752
|
+
const records = opts.includeRaw ? fused : stripRaw(fused);
|
|
753
|
+
console.log(format(records, columns, fmt, ctx));
|
|
754
|
+
}
|
|
755
|
+
async function runDoctor(program, opts) {
|
|
756
|
+
const startedAt = Date.now();
|
|
757
|
+
const fmt = detectFormat(program.opts().format);
|
|
758
|
+
const ctx = makeCtx("patent.doctor", startedAt);
|
|
759
|
+
const adapters = listPatentAdapters().filter((a) => {
|
|
760
|
+
const list = resolveSources(opts.sources, listPatentAdapters().map((x) => x.name));
|
|
761
|
+
return list.includes(a.name);
|
|
762
|
+
});
|
|
763
|
+
const rows = [];
|
|
764
|
+
let anyError = false;
|
|
765
|
+
let anyDishonesty = false;
|
|
766
|
+
for (const adapter of adapters) {
|
|
767
|
+
const caps = new Set();
|
|
768
|
+
for (const cmd of Object.values(adapter.commands)) {
|
|
769
|
+
for (const cap of cmd.capabilities ?? []) {
|
|
770
|
+
if (cap.startsWith("patent."))
|
|
771
|
+
caps.add(cap);
|
|
772
|
+
}
|
|
773
|
+
}
|
|
774
|
+
const commandNames = Object.keys(adapter.commands);
|
|
775
|
+
const verificationStatus = resolveAdapterVerificationStatus(adapter.name, commandNames);
|
|
776
|
+
const healthCmd = resolveCommand(adapter.name, "health");
|
|
777
|
+
if (!healthCmd) {
|
|
778
|
+
rows.push({
|
|
779
|
+
source: adapter.name,
|
|
780
|
+
capabilities: [...caps].sort(),
|
|
781
|
+
health: "skipped",
|
|
782
|
+
verification_status: verificationStatus,
|
|
783
|
+
detail: "no `health` command — adapter passes by introspection only",
|
|
784
|
+
});
|
|
785
|
+
// Honesty gate: an adapter claiming "verified" must own a reachable
|
|
786
|
+
// health probe. Without one, the claim is unfalsifiable in this
|
|
787
|
+
// session — surface the mismatch as an error.
|
|
788
|
+
if (verificationStatus === "verified") {
|
|
789
|
+
anyDishonesty = true;
|
|
790
|
+
}
|
|
791
|
+
continue;
|
|
792
|
+
}
|
|
793
|
+
const strategy = commandStrategy(adapter, healthCmd.command);
|
|
794
|
+
if (strategy !== undefined && strategy !== Strategy.PUBLIC) {
|
|
795
|
+
rows.push({
|
|
796
|
+
source: adapter.name,
|
|
797
|
+
capabilities: [...caps].sort(),
|
|
798
|
+
health: "blocked",
|
|
799
|
+
verification_status: verificationStatus,
|
|
800
|
+
detail: `health probe requires ${strategy} auth — skipped`,
|
|
801
|
+
});
|
|
802
|
+
// A `verified` header must not co-exist with a strategy gate; the
|
|
803
|
+
// probe could not have run in CI without auth. Flag the mismatch.
|
|
804
|
+
if (verificationStatus === "verified") {
|
|
805
|
+
anyDishonesty = true;
|
|
806
|
+
}
|
|
807
|
+
continue;
|
|
808
|
+
}
|
|
809
|
+
const inv = buildInvocation("cli", adapter.name, "health", { args: {}, source: "internal" }, { approved: true });
|
|
810
|
+
if (!inv) {
|
|
811
|
+
rows.push({
|
|
812
|
+
source: adapter.name,
|
|
813
|
+
capabilities: [...caps].sort(),
|
|
814
|
+
health: "error",
|
|
815
|
+
verification_status: verificationStatus,
|
|
816
|
+
detail: "could not build invocation for health command",
|
|
817
|
+
});
|
|
818
|
+
anyError = true;
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
const result = await execute(inv);
|
|
822
|
+
if (result.error) {
|
|
823
|
+
rows.push({
|
|
824
|
+
source: adapter.name,
|
|
825
|
+
capabilities: [...caps].sort(),
|
|
826
|
+
health: "error",
|
|
827
|
+
verification_status: verificationStatus,
|
|
828
|
+
detail: result.error.message ?? result.error.code ?? "health failed",
|
|
829
|
+
});
|
|
830
|
+
anyError = true;
|
|
831
|
+
if (verificationStatus === "verified") {
|
|
832
|
+
anyDishonesty = true;
|
|
833
|
+
}
|
|
834
|
+
continue;
|
|
835
|
+
}
|
|
836
|
+
rows.push({
|
|
837
|
+
source: adapter.name,
|
|
838
|
+
capabilities: [...caps].sort(),
|
|
839
|
+
health: "ok",
|
|
840
|
+
verification_status: verificationStatus,
|
|
841
|
+
detail: "health probe returned successfully",
|
|
842
|
+
});
|
|
843
|
+
}
|
|
844
|
+
ctx.duration_ms = Date.now() - startedAt;
|
|
845
|
+
ctx.surface = "web";
|
|
846
|
+
console.log(format(rows, ["source", "capabilities", "health", "verification_status", "detail"], fmt, ctx));
|
|
847
|
+
if (anyError || anyDishonesty)
|
|
848
|
+
process.exit(ExitCode.GENERIC_ERROR);
|
|
849
|
+
}
|
|
850
|
+
// ── Registration ────────────────────────────────────────────────────────
|
|
851
|
+
export function registerPatentCommand(program) {
|
|
852
|
+
const patent = program
|
|
853
|
+
.command("patent")
|
|
854
|
+
.description("Patent meta-command — search, retrieve, and audit across L0/L1/L2 patent adapters");
|
|
855
|
+
patent
|
|
856
|
+
.command("search <query>")
|
|
857
|
+
.description("Free-text + filter search across patent sources")
|
|
858
|
+
.option("--sources <csv>", "comma-separated source list, or `all` for every patent adapter")
|
|
859
|
+
.option("--limit <n>", "maximum fused result count", "20")
|
|
860
|
+
.option("--since <YYYY>", "earliest filing year")
|
|
861
|
+
.option("--cpc <csv>", "Cooperative Patent Classification filter")
|
|
862
|
+
.option("-D, --detailed", "emit every PatentRecord field (rich block / wide table)")
|
|
863
|
+
.option("--include-raw", "preserve the upstream `raw` payload on each record")
|
|
864
|
+
.option("-f, --format <fmt>", "output format: md (default), json, jsonl, csv — overrides global -f")
|
|
865
|
+
.action(async (query, opts) => {
|
|
866
|
+
await runSearch(program, query, opts);
|
|
867
|
+
});
|
|
868
|
+
patent
|
|
869
|
+
.command("get <publication-number>")
|
|
870
|
+
.description("Retrieve one patent record by ST.16 publication number")
|
|
871
|
+
.option("-D, --detailed", "emit every PatentRecord field (rich block)")
|
|
872
|
+
.option("--include-raw", "preserve the upstream `raw` payload")
|
|
873
|
+
.option("-f, --format <fmt>", "output format: md (default), json, jsonl, csv")
|
|
874
|
+
.action(async (publicationNumber, opts) => {
|
|
875
|
+
await runGet(program, publicationNumber, opts);
|
|
876
|
+
});
|
|
877
|
+
patent
|
|
878
|
+
.command("family <publication-number>")
|
|
879
|
+
.description("INPADOC / DOCDB family lookup brokered through EPO Espacenet")
|
|
880
|
+
.action(async (publicationNumber) => {
|
|
881
|
+
await runFamily(program, publicationNumber);
|
|
882
|
+
});
|
|
883
|
+
patent
|
|
884
|
+
.command("citations <publication-number>")
|
|
885
|
+
.description("Citing / cited records for the given publication number")
|
|
886
|
+
.option("--direction <dir>", "citing (records that cite this) or cited (records this cites)", "citing")
|
|
887
|
+
.action(async (publicationNumber, opts) => {
|
|
888
|
+
await runCitations(program, publicationNumber, opts);
|
|
889
|
+
});
|
|
890
|
+
patent
|
|
891
|
+
.command("legal-status <publication-numbers...>")
|
|
892
|
+
.description("Prosecution / grant status for one or more publication numbers")
|
|
893
|
+
.action(async (publicationNumbers) => {
|
|
894
|
+
await runLegalStatus(program, publicationNumbers);
|
|
895
|
+
});
|
|
896
|
+
patent
|
|
897
|
+
.command("prior-art")
|
|
898
|
+
.description("Semantic + keyword + CPC prior-art fusion against an abstract")
|
|
899
|
+
.requiredOption("--abstract <text>", "candidate abstract text")
|
|
900
|
+
.option("--sources <csv>", "comma-separated source list (default: pqai,google-patents-bq,epo)")
|
|
901
|
+
.option("--top <n>", "top-N fused results to return", "20")
|
|
902
|
+
.option("-D, --detailed", "emit every PatentRecord field (rich block)")
|
|
903
|
+
.option("--include-raw", "preserve the upstream `raw` payload")
|
|
904
|
+
.option("-f, --format <fmt>", "output format: md (default), json, jsonl, csv")
|
|
905
|
+
.action(async (opts) => {
|
|
906
|
+
await runPriorArt(program, opts);
|
|
907
|
+
});
|
|
908
|
+
patent
|
|
909
|
+
.command("doctor")
|
|
910
|
+
.description("Probe each registered patent adapter for health and schema drift")
|
|
911
|
+
.option("--sources <csv>", "limit to a comma-separated source list")
|
|
912
|
+
.action(async (opts) => {
|
|
913
|
+
await runDoctor(program, opts);
|
|
914
|
+
});
|
|
915
|
+
}
|
|
916
|
+
// Re-export some helpers so the test suite can exercise them without
|
|
917
|
+
// going through Commander; rule 03 forbids mocking owned modules.
|
|
918
|
+
export { JURISDICTION_ADAPTERS, FAMILY_BROKER, DEFAULT_SOURCES };
|
|
919
|
+
//# sourceMappingURL=patent.js.map
|