@jackwener/opencli 1.7.12 → 1.7.13
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/README.md +8 -7
- package/README.zh-CN.md +9 -8
- package/cli-manifest.json +12194 -6843
- package/clis/1point3acres/digest.js +35 -0
- package/clis/1point3acres/forum.js +51 -0
- package/clis/1point3acres/forums.js +44 -0
- package/clis/1point3acres/hot.js +35 -0
- package/clis/1point3acres/latest.js +35 -0
- package/clis/1point3acres/notifications.js +64 -0
- package/clis/1point3acres/search.js +71 -0
- package/clis/1point3acres/thread.js +117 -0
- package/clis/1point3acres/user.js +77 -0
- package/clis/1point3acres/utils.js +247 -0
- package/clis/_shared/desktop-commands.js +4 -0
- package/clis/aibase/news.js +110 -0
- package/clis/aibase/news.test.js +59 -0
- package/clis/amazon/discussion.test.js +1 -28
- package/clis/antigravity/watch.js +3 -2
- package/clis/arxiv/author.js +44 -0
- package/clis/baidu-scholar/search.js +0 -1
- package/clis/bbc/topic.js +57 -0
- package/clis/bbc/utils.js +79 -0
- package/clis/chaoxing/assignments.js +1 -1
- package/clis/chaoxing/exams.js +1 -1
- package/clis/chatgpt/ask.js +57 -0
- package/clis/chatgpt/commands.test.js +45 -0
- package/clis/chatgpt/detail.js +46 -0
- package/clis/chatgpt/history.js +39 -0
- package/clis/chatgpt/image.js +12 -11
- package/clis/chatgpt/image.test.js +23 -0
- package/clis/chatgpt/new.js +25 -0
- package/clis/chatgpt/read.js +43 -0
- package/clis/chatgpt/send.js +46 -0
- package/clis/chatgpt/status.js +29 -0
- package/clis/chatgpt/utils.js +294 -4
- package/clis/chatgpt/utils.test.js +13 -0
- package/clis/chatgpt-app/ask.js +6 -3
- package/clis/chatwise/ask.js +16 -43
- package/clis/chatwise/composer.test.js +186 -0
- package/clis/chatwise/send.js +2 -24
- package/clis/chatwise/utils.js +143 -0
- package/clis/claude/ask.js +1 -1
- package/clis/claude/detail.js +1 -0
- package/clis/claude/history.js +1 -0
- package/clis/claude/new.js +1 -0
- package/clis/claude/read.js +1 -0
- package/clis/claude/send.js +1 -0
- package/clis/claude/status.js +1 -0
- package/clis/codex/ask.js +15 -9
- package/clis/codex/history.js +16 -33
- package/clis/codex/projects.js +28 -0
- package/clis/codex/read.js +10 -4
- package/clis/codex/send.js +10 -3
- package/clis/codex/sidebar.js +356 -0
- package/clis/codex/sidebar.test.js +329 -0
- package/clis/coingecko/categories.js +75 -0
- package/clis/coingecko/coin.js +107 -0
- package/clis/coingecko/coingecko.test.js +109 -0
- package/clis/coingecko/derivatives.js +84 -0
- package/clis/coingecko/exchanges.js +74 -0
- package/clis/coingecko/global.js +71 -0
- package/clis/coingecko/top.js +64 -0
- package/clis/coingecko/trending.js +55 -0
- package/clis/coupang/add-to-cart.js +21 -13
- package/clis/coupang/coupang.test.js +159 -0
- package/clis/coupang/product.js +257 -0
- package/clis/coupang/search.js +38 -16
- package/clis/coupang/utils.js +55 -1
- package/clis/crates/crate.js +62 -0
- package/clis/crates/search.js +44 -0
- package/clis/crates/utils.js +72 -0
- package/clis/ctrip/ctrip.test.js +234 -0
- package/clis/ctrip/hotel-suggest.js +45 -0
- package/clis/ctrip/search.js +22 -68
- package/clis/ctrip/utils.js +175 -0
- package/clis/cursor/ask.js +6 -3
- package/clis/dblp/author.js +133 -0
- package/clis/dblp/venue.js +64 -0
- package/clis/deepseek/ask.js +12 -7
- package/clis/deepseek/ask.test.js +13 -13
- package/clis/deepseek/detail.js +38 -0
- package/clis/deepseek/detail.test.js +81 -0
- package/clis/deepseek/history.js +1 -0
- package/clis/deepseek/new.js +1 -0
- package/clis/deepseek/read.js +1 -0
- package/clis/deepseek/send.js +140 -0
- package/clis/deepseek/send.test.js +107 -0
- package/clis/deepseek/status.js +1 -0
- package/clis/deepseek/utils.js +66 -0
- package/clis/deepseek/utils.test.js +107 -1
- package/clis/defillama/defillama.test.js +99 -0
- package/clis/defillama/protocol.js +84 -0
- package/clis/defillama/protocols.js +55 -0
- package/clis/defillama/utils.js +99 -0
- package/clis/devto/latest.js +74 -0
- package/clis/dockerhub/image.js +52 -0
- package/clis/dockerhub/search.js +47 -0
- package/clis/dockerhub/utils.js +100 -0
- package/clis/doubao/ask.js +7 -3
- package/clis/doubao/detail.js +1 -0
- package/clis/doubao/history.js +1 -0
- package/clis/doubao/meeting-summary.js +1 -0
- package/clis/doubao/meeting-transcript.js +1 -0
- package/clis/doubao/new.js +1 -0
- package/clis/doubao/read.js +1 -0
- package/clis/doubao/send.js +1 -0
- package/clis/doubao/status.js +1 -0
- package/clis/douyin/draft.test.js +1 -30
- package/clis/endoflife/endoflife.test.js +51 -0
- package/clis/endoflife/product.js +55 -0
- package/clis/endoflife/utils.js +89 -0
- package/clis/facebook/__fixtures__/notifications-page.html +13 -0
- package/clis/facebook/notifications.js +326 -30
- package/clis/facebook/notifications.test.js +458 -0
- package/clis/flathub/app.js +71 -0
- package/clis/flathub/flathub.test.js +90 -0
- package/clis/flathub/search.js +80 -0
- package/clis/flathub/utils.js +114 -0
- package/clis/gemini/ask.js +7 -3
- package/clis/gemini/ask.test.js +2 -2
- package/clis/gemini/deep-research-result.js +6 -2
- package/clis/gemini/deep-research-result.test.js +15 -14
- package/clis/gemini/deep-research.js +8 -4
- package/clis/gemini/deep-research.test.js +15 -18
- package/clis/gemini/image.js +7 -2
- package/clis/gemini/new.js +1 -0
- package/clis/gemini/utils.js +0 -4
- package/clis/google-scholar/cite.js +0 -1
- package/clis/google-scholar/profile.js +0 -1
- package/clis/google-scholar/search.js +0 -1
- package/clis/goproxy/goproxy.test.js +103 -0
- package/clis/goproxy/module.js +47 -0
- package/clis/goproxy/utils.js +165 -0
- package/clis/goproxy/versions.js +59 -0
- package/clis/gov-law/recent.js +0 -1
- package/clis/gov-law/search.js +0 -1
- package/clis/gov-policy/__fixtures__/recent.html +16 -0
- package/clis/gov-policy/__fixtures__/search.html +41 -0
- package/clis/gov-policy/gov-policy.test.js +224 -0
- package/clis/gov-policy/recent.js +66 -24
- package/clis/gov-policy/search.js +65 -23
- package/clis/gov-policy/utils.js +54 -0
- package/clis/grok/ask.js +49 -265
- package/clis/grok/ask.test.js +21 -46
- package/clis/grok/detail.js +60 -0
- package/clis/grok/history.js +48 -0
- package/clis/grok/{image.ts → image.js} +56 -70
- package/clis/grok/image.test.ts +20 -0
- package/clis/grok/new.js +20 -0
- package/clis/grok/read.js +39 -0
- package/clis/grok/send.js +50 -0
- package/clis/grok/status.js +41 -0
- package/clis/grok/utils.js +326 -0
- package/clis/grok/utils.test.js +103 -0
- package/clis/hf/datasets.js +88 -0
- package/clis/hf/hf.test.js +16 -0
- package/clis/hf/models.js +91 -0
- package/clis/hf/paper.js +79 -0
- package/clis/hf/spaces.js +101 -0
- package/clis/hf/top.js +1 -0
- package/clis/homebrew/cask.js +39 -0
- package/clis/homebrew/formula.js +41 -0
- package/clis/homebrew/popular.js +54 -0
- package/clis/homebrew/utils.js +100 -0
- package/clis/hupu/__fixtures__/hot-home.html +64 -0
- package/clis/hupu/detail.js +0 -1
- package/clis/hupu/hot.js +156 -35
- package/clis/hupu/hot.test.js +224 -0
- package/clis/hupu/search.js +0 -1
- package/clis/instagram/note.js +1 -1
- package/clis/instagram/note.test.js +1 -29
- package/clis/instagram/post.js +1 -1
- package/clis/instagram/post.test.js +1 -1
- package/clis/instagram/reel.js +1 -1
- package/clis/instagram/story.js +1 -1
- package/clis/instagram/story.test.js +1 -34
- package/clis/jd/commands.test.js +1 -24
- package/clis/lichess/lichess.test.js +85 -0
- package/clis/lichess/top.js +46 -0
- package/clis/lichess/user.js +91 -0
- package/clis/lichess/utils.js +97 -0
- package/clis/linkedin/search.js +107 -10
- package/clis/linkedin/search.test.js +222 -0
- package/clis/linux-do/feed.js +2 -5
- package/clis/linux-do/feed.test.js +35 -0
- package/clis/lobsters/domain.js +92 -0
- package/clis/maven/artifact.js +49 -0
- package/clis/maven/search.js +51 -0
- package/clis/maven/utils.js +110 -0
- package/clis/mdn/search.js +97 -0
- package/clis/medium/tag.js +135 -0
- package/clis/npm/downloads.js +59 -0
- package/clis/npm/package.js +70 -0
- package/clis/npm/search.js +49 -0
- package/clis/npm/utils.js +76 -0
- package/clis/nuget/nuget.test.js +111 -0
- package/clis/nuget/package.js +101 -0
- package/clis/nuget/search.js +69 -0
- package/clis/nuget/utils.js +87 -0
- package/clis/nvd/cve.js +121 -0
- package/clis/oeis/oeis.test.js +88 -0
- package/clis/oeis/search.js +63 -0
- package/clis/oeis/sequence.js +71 -0
- package/clis/oeis/utils.js +88 -0
- package/clis/openalex/search.js +69 -0
- package/clis/openalex/utils.js +160 -0
- package/clis/openalex/work.js +65 -0
- package/clis/openfda/drug-label.js +74 -0
- package/clis/openfda/food-recall.js +65 -0
- package/clis/openfda/openfda.test.js +114 -0
- package/clis/openfda/utils.js +67 -0
- package/clis/osv/osv.test.js +97 -0
- package/clis/osv/query.js +72 -0
- package/clis/osv/utils.js +169 -0
- package/clis/osv/vulnerability.js +54 -0
- package/clis/packagist/package.js +49 -0
- package/clis/packagist/search.js +43 -0
- package/clis/packagist/utils.js +113 -0
- package/clis/paperreview/feedback.js +1 -1
- package/clis/paperreview/review.js +1 -1
- package/clis/paperreview/submit.js +1 -1
- package/clis/pixiv/download.test.js +1 -1
- package/clis/pixiv/illusts.test.js +1 -1
- package/clis/pixiv/search.test.js +1 -1
- package/clis/pubmed/article.js +50 -0
- package/clis/pubmed/author.js +64 -0
- package/clis/pubmed/citations.js +36 -0
- package/clis/pubmed/pubmed.test.js +276 -0
- package/clis/pubmed/related.js +45 -0
- package/clis/pubmed/search.js +75 -0
- package/clis/pubmed/utils.js +309 -0
- package/clis/pypi/downloads.js +66 -0
- package/clis/pypi/package.js +79 -0
- package/clis/pypi/utils.js +55 -0
- package/clis/quark/mv.js +1 -1
- package/clis/quark/save.js +1 -1
- package/clis/qwen/ask.js +85 -0
- package/clis/qwen/detail.js +62 -0
- package/clis/qwen/history.js +61 -0
- package/clis/qwen/image.js +179 -0
- package/clis/qwen/new.js +23 -0
- package/clis/qwen/read.js +41 -0
- package/clis/qwen/send.js +55 -0
- package/clis/qwen/status.js +37 -0
- package/clis/qwen/utils.js +409 -0
- package/clis/qwen/utils.test.js +45 -0
- package/clis/rest-countries/country.js +65 -0
- package/clis/rest-countries/region.js +64 -0
- package/clis/rest-countries/rest-countries.test.js +83 -0
- package/clis/rest-countries/utils.js +126 -0
- package/clis/reuters/article-detail.js +53 -0
- package/clis/reuters/reuters.test.js +299 -0
- package/clis/reuters/search.js +45 -34
- package/clis/reuters/utils.js +159 -0
- package/clis/rfc/rfc.js +52 -0
- package/clis/rfc/rfc.test.js +74 -0
- package/clis/rfc/utils.js +72 -0
- package/clis/rubygems/gem.js +42 -0
- package/clis/rubygems/search.js +47 -0
- package/clis/rubygems/utils.js +86 -0
- package/clis/stackoverflow/related.js +66 -0
- package/clis/stackoverflow/stackoverflow.test.js +58 -0
- package/clis/stackoverflow/tag.js +60 -0
- package/clis/stackoverflow/user.js +50 -0
- package/clis/stackoverflow/utils.js +118 -0
- package/clis/steam/app.js +67 -0
- package/clis/steam/search.js +58 -0
- package/clis/steam/steam.test.js +46 -0
- package/clis/steam/utils.js +107 -0
- package/clis/taobao/commands.test.js +1 -24
- package/clis/test-utils.js +61 -0
- package/clis/tieba/hot.js +0 -1
- package/clis/tiktok/comment.js +128 -41
- package/clis/tiktok/creator-videos.js +270 -0
- package/clis/tiktok/creator-videos.test.js +113 -0
- package/clis/tiktok/explore.js +137 -29
- package/clis/tiktok/follow.js +115 -33
- package/clis/tiktok/following.js +157 -36
- package/clis/tiktok/friends.js +139 -37
- package/clis/tiktok/live.js +137 -41
- package/clis/tiktok/notifications.js +141 -38
- package/clis/tiktok/refactor.test.js +389 -0
- package/clis/tiktok/unfollow.js +124 -38
- package/clis/tiktok/user.js +203 -29
- package/clis/tiktok/utils.js +505 -0
- package/clis/tiktok/write-refactor.test.js +370 -0
- package/clis/toutiao/articles.js +36 -62
- package/clis/toutiao/hot.js +63 -0
- package/clis/toutiao/toutiao.test.js +378 -0
- package/clis/toutiao/utils.js +161 -0
- package/clis/tvmaze/search.js +61 -0
- package/clis/tvmaze/show.js +60 -0
- package/clis/tvmaze/tvmaze.test.js +93 -0
- package/clis/tvmaze/utils.js +110 -0
- package/clis/twitter/accept.js +1 -1
- package/clis/twitter/followers.js +134 -69
- package/clis/twitter/reply-dm.js +1 -1
- package/clis/twitter/reply.test.js +1 -29
- package/clis/uisdc/news.js +105 -0
- package/clis/uisdc/news.test.js +66 -0
- package/clis/wanfang/search.js +0 -1
- package/clis/web/read.js +47 -17
- package/clis/web/read.test.js +101 -1
- package/clis/weixin/create-draft.js +1 -1
- package/clis/weixin/drafts.js +1 -1
- package/clis/weixin/drafts.test.js +5 -1
- package/clis/weixin/search.js +157 -0
- package/clis/weixin/search.test.js +227 -0
- package/clis/wikidata/entity.js +60 -0
- package/clis/wikidata/search.js +50 -0
- package/clis/wikidata/utils.js +117 -0
- package/clis/wikidata/wikidata.test.js +83 -0
- package/clis/wikipedia/page.js +95 -0
- package/clis/wttr/current.js +63 -0
- package/clis/wttr/forecast.js +71 -0
- package/clis/wttr/utils.js +50 -0
- package/clis/wttr/wttr.test.js +84 -0
- package/clis/xianyu/chat.js +16 -4
- package/clis/xianyu/chat.test.js +64 -0
- package/clis/xianyu/publish.js +485 -0
- package/clis/xianyu/publish.test.js +220 -0
- package/clis/xiaoe/catalog.js +105 -40
- package/clis/xiaoe/content.js +164 -29
- package/clis/xiaoe/courses.js +86 -29
- package/clis/xiaoe/xiaoe.test.js +486 -0
- package/clis/xiaohongshu/creator-notes-summary.js +1 -1
- package/clis/xiaohongshu/publish.js +16 -3
- package/clis/xiaohongshu/publish.test.js +46 -1
- package/clis/youtube/transcript.js +13 -19
- package/clis/youtube/transcript.test.js +17 -0
- package/clis/yuanbao/ask.js +17 -66
- package/clis/yuanbao/ask.test.js +5 -5
- package/clis/yuanbao/detail.js +65 -0
- package/clis/yuanbao/history.js +51 -0
- package/clis/yuanbao/new.js +1 -0
- package/clis/yuanbao/read.js +38 -0
- package/clis/yuanbao/send.js +57 -0
- package/clis/yuanbao/shared.js +297 -5
- package/clis/yuanbao/shared.test.js +80 -0
- package/clis/yuanbao/status.js +44 -0
- package/clis/zlibrary/commands.test.js +1 -11
- package/dist/src/browser/base-page.d.ts +9 -0
- package/dist/src/browser/base-page.js +44 -1
- package/dist/src/browser/base-page.test.js +66 -0
- package/dist/src/browser/cdp.d.ts +1 -0
- package/dist/src/browser/cdp.js +51 -9
- package/dist/src/browser/daemon-client.d.ts +4 -0
- package/dist/src/browser/errors.js +1 -1
- package/dist/src/browser/page.d.ts +1 -1
- package/dist/src/browser/page.js +3 -1
- package/dist/src/browser/page.test.js +29 -0
- package/dist/src/browser/target-errors.d.ts +2 -1
- package/dist/src/browser/target-errors.js +1 -0
- package/dist/src/browser/target-resolver.d.ts +25 -0
- package/dist/src/browser/target-resolver.js +43 -0
- package/dist/src/build-manifest.js +9 -4
- package/dist/src/build-manifest.test.js +2 -8
- package/dist/src/capabilityRouting.d.ts +16 -1
- package/dist/src/capabilityRouting.js +24 -1
- package/dist/src/capabilityRouting.test.js +19 -1
- package/dist/src/cli.js +76 -11
- package/dist/src/cli.test.js +150 -0
- package/dist/src/commanderAdapter.js +0 -5
- package/dist/src/commanderAdapter.test.js +0 -1
- package/dist/src/discovery.js +2 -5
- package/dist/src/errors.js +1 -1
- package/dist/src/execution.d.ts +1 -1
- package/dist/src/execution.js +111 -27
- package/dist/src/execution.test.js +326 -17
- package/dist/src/help.d.ts +23 -2
- package/dist/src/help.js +41 -19
- package/dist/src/help.test.d.ts +1 -0
- package/dist/src/help.test.js +54 -0
- package/dist/src/main.js +14 -1
- package/dist/src/manifest-types.d.ts +5 -3
- package/dist/src/pipeline/executor.js +1 -1
- package/dist/src/pipeline/executor.test.js +8 -0
- package/dist/src/pipeline/registry.d.ts +9 -0
- package/dist/src/pipeline/registry.js +13 -1
- package/dist/src/pipeline/steps/browser.d.ts +1 -0
- package/dist/src/pipeline/steps/browser.js +10 -0
- package/dist/src/pipeline/steps/download.test.js +1 -0
- package/dist/src/registry-api.d.ts +1 -1
- package/dist/src/registry.d.ts +12 -11
- package/dist/src/registry.js +16 -6
- package/dist/src/registry.test.js +2 -2
- package/dist/src/runtime.d.ts +2 -1
- package/dist/src/runtime.js +1 -1
- package/dist/src/serialization.d.ts +2 -2
- package/dist/src/serialization.js +4 -6
- package/dist/src/serialization.test.js +17 -0
- package/dist/src/types.d.ts +17 -0
- package/dist/src/validate.js +15 -11
- package/dist/src/validate.test.d.ts +9 -0
- package/dist/src/validate.test.js +90 -0
- package/package.json +1 -1
- package/scripts/fetch-adapters.js +1 -1
- package/scripts/typed-error-lint-baseline.json +5 -77
- package/clis/ctrip/search.test.js +0 -64
- package/clis/gov-policy/commands.test.js +0 -27
- package/clis/linux-do/category.js +0 -37
- package/clis/linux-do/hot.js +0 -26
- package/clis/linux-do/latest.js +0 -19
- package/clis/pixiv/test-utils.js +0 -23
- package/clis/toutiao/articles.test.js +0 -30
- package/dist/src/analysis.d.ts +0 -40
- package/dist/src/analysis.js +0 -172
package/clis/hf/paper.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// hf paper — fetch a single Hugging Face paper page (mirrors arXiv id),
|
|
2
|
+
// returning the full title / summary / upvote count / authors and the
|
|
3
|
+
// AI-generated keyword list HF curates.
|
|
4
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
5
|
+
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
6
|
+
|
|
7
|
+
const ARXIV_ID_PATTERN = /^\d{4}\.\d{4,5}(?:v\d+)?$/;
|
|
8
|
+
|
|
9
|
+
cli({
|
|
10
|
+
site: 'hf',
|
|
11
|
+
name: 'paper',
|
|
12
|
+
access: 'read',
|
|
13
|
+
description: 'Hugging Face paper detail by arXiv id (full title / summary / authors / AI keywords)',
|
|
14
|
+
domain: 'huggingface.co',
|
|
15
|
+
strategy: Strategy.PUBLIC,
|
|
16
|
+
browser: false,
|
|
17
|
+
args: [
|
|
18
|
+
{ name: 'id', positional: true, required: true, help: 'arXiv id (e.g. "1706.03762") — same value HF uses to mirror the paper' },
|
|
19
|
+
],
|
|
20
|
+
columns: ['id', 'title', 'authors', 'publishedAt', 'upvotes', 'aiKeywords', 'summary', 'aiSummary', 'url'],
|
|
21
|
+
func: async (args) => {
|
|
22
|
+
const raw = String(args.id ?? '').trim();
|
|
23
|
+
if (!raw) {
|
|
24
|
+
throw new ArgumentError('hf paper id cannot be empty', 'Example: opencli hf paper 1706.03762');
|
|
25
|
+
}
|
|
26
|
+
if (!ARXIV_ID_PATTERN.test(raw)) {
|
|
27
|
+
throw new ArgumentError(
|
|
28
|
+
`hf paper id "${args.id}" is not a valid arXiv id`,
|
|
29
|
+
'Expected the modern arXiv form `YYMM.NNNNN` (optionally with a version suffix like `v3`).',
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
const endpoint = process.env.HF_ENDPOINT?.replace(/\/+$/, '') || 'https://huggingface.co';
|
|
33
|
+
const url = `${endpoint}/api/papers/${encodeURIComponent(raw)}`;
|
|
34
|
+
let resp;
|
|
35
|
+
try {
|
|
36
|
+
resp = await fetch(url, { headers: { accept: 'application/json' } });
|
|
37
|
+
}
|
|
38
|
+
catch (err) {
|
|
39
|
+
throw new CommandExecutionError(`hf paper request failed: ${err?.message ?? err}`);
|
|
40
|
+
}
|
|
41
|
+
if (resp.status === 404) {
|
|
42
|
+
throw new EmptyResultError('hf paper', `Hugging Face has no paper page for "${raw}".`);
|
|
43
|
+
}
|
|
44
|
+
if (resp.status === 429) {
|
|
45
|
+
throw new CommandExecutionError(
|
|
46
|
+
'hf paper returned HTTP 429 (rate limited)',
|
|
47
|
+
'Hugging Face throttles unauthenticated traffic; wait a few seconds and retry.',
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
if (!resp.ok) {
|
|
51
|
+
throw new CommandExecutionError(`hf paper returned HTTP ${resp.status}`);
|
|
52
|
+
}
|
|
53
|
+
let body;
|
|
54
|
+
try {
|
|
55
|
+
body = await resp.json();
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
throw new CommandExecutionError(`hf paper returned malformed JSON: ${err?.message ?? err}`);
|
|
59
|
+
}
|
|
60
|
+
if (!body || typeof body !== 'object' || !body.id) {
|
|
61
|
+
throw new EmptyResultError('hf paper', `Hugging Face returned no paper data for "${raw}".`);
|
|
62
|
+
}
|
|
63
|
+
const authors = Array.isArray(body.authors)
|
|
64
|
+
? body.authors.map((a) => (typeof a === 'object' && a ? (a.name || a.fullname || '') : String(a))).filter(Boolean)
|
|
65
|
+
: [];
|
|
66
|
+
const aiKeywords = Array.isArray(body.ai_keywords) ? body.ai_keywords.join(', ') : '';
|
|
67
|
+
return [{
|
|
68
|
+
id: String(body.id),
|
|
69
|
+
title: String(body.title ?? ''),
|
|
70
|
+
authors: authors.join(', '),
|
|
71
|
+
publishedAt: String(body.publishedAt ?? '').slice(0, 10),
|
|
72
|
+
upvotes: body.upvotes != null ? Number(body.upvotes) : null,
|
|
73
|
+
aiKeywords,
|
|
74
|
+
summary: String(body.summary ?? ''),
|
|
75
|
+
aiSummary: String(body.ai_summary ?? ''),
|
|
76
|
+
url: `${endpoint}/papers/${body.id}`,
|
|
77
|
+
}];
|
|
78
|
+
},
|
|
79
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// hf spaces — list top Hugging Face Spaces (gradio / streamlit / static demos).
|
|
2
|
+
//
|
|
3
|
+
// Hits `https://huggingface.co/api/spaces?sort=…&full=true`. Mirrors the shape
|
|
4
|
+
// of `hf models` / `hf datasets`. The Spaces API does not expose `trending` as
|
|
5
|
+
// a sort key (verified live: returns "Invalid sort parameter"), so the allowed
|
|
6
|
+
// sort set is narrower.
|
|
7
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
8
|
+
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
9
|
+
|
|
10
|
+
const SORT_OPTIONS = ['likes', 'created_at', 'last_modified'];
|
|
11
|
+
const SORT_ALIAS = { lastmodified: 'last_modified', createdat: 'created_at' };
|
|
12
|
+
|
|
13
|
+
cli({
|
|
14
|
+
site: 'hf',
|
|
15
|
+
name: 'spaces',
|
|
16
|
+
access: 'read',
|
|
17
|
+
description: 'Top Hugging Face Spaces (likes / created_at / last_modified).',
|
|
18
|
+
domain: 'huggingface.co',
|
|
19
|
+
strategy: Strategy.PUBLIC,
|
|
20
|
+
browser: false,
|
|
21
|
+
args: [
|
|
22
|
+
{ name: 'sort', type: 'string', default: 'likes', help: `Sort key: ${SORT_OPTIONS.join(', ')}` },
|
|
23
|
+
{ name: 'search', type: 'string', required: false, help: 'Optional name/owner substring filter (e.g. "stability", "openai/")' },
|
|
24
|
+
{ name: 'sdk', type: 'string', required: false, help: 'Filter by Space SDK: gradio / streamlit / docker / static' },
|
|
25
|
+
{ name: 'limit', type: 'int', default: 20, help: 'Max spaces (max 100; one API page).' },
|
|
26
|
+
],
|
|
27
|
+
columns: ['rank', 'id', 'author', 'sdk', 'likes', 'tags', 'lastModified', 'url'],
|
|
28
|
+
func: async (args) => {
|
|
29
|
+
const sortRaw = String(args.sort ?? 'likes').toLowerCase();
|
|
30
|
+
const sort = SORT_ALIAS[sortRaw] ?? sortRaw;
|
|
31
|
+
if (!SORT_OPTIONS.includes(sort)) {
|
|
32
|
+
throw new ArgumentError(`hf spaces sort must be one of ${SORT_OPTIONS.join(', ')}`);
|
|
33
|
+
}
|
|
34
|
+
const limit = Number(args.limit ?? 20);
|
|
35
|
+
if (!Number.isInteger(limit) || limit <= 0) {
|
|
36
|
+
throw new ArgumentError('hf spaces limit must be a positive integer');
|
|
37
|
+
}
|
|
38
|
+
if (limit > 100) {
|
|
39
|
+
throw new ArgumentError('hf spaces limit must be <= 100');
|
|
40
|
+
}
|
|
41
|
+
const sdk = args.sdk == null ? '' : String(args.sdk).trim().toLowerCase();
|
|
42
|
+
const allowedSdks = new Set(['', 'gradio', 'streamlit', 'docker', 'static']);
|
|
43
|
+
if (!allowedSdks.has(sdk)) {
|
|
44
|
+
throw new ArgumentError(`hf spaces sdk must be one of gradio / streamlit / docker / static`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const url = new URL('https://huggingface.co/api/spaces');
|
|
48
|
+
url.searchParams.set('sort', sort);
|
|
49
|
+
url.searchParams.set('direction', '-1');
|
|
50
|
+
url.searchParams.set('limit', String(limit));
|
|
51
|
+
url.searchParams.set('full', 'true');
|
|
52
|
+
if (args.search) url.searchParams.set('search', String(args.search));
|
|
53
|
+
if (sdk) url.searchParams.set('sdk', sdk);
|
|
54
|
+
|
|
55
|
+
let resp;
|
|
56
|
+
try {
|
|
57
|
+
resp = await fetch(url, {
|
|
58
|
+
headers: { Accept: 'application/json', 'User-Agent': 'opencli/1.0 (+https://github.com/jackwener/opencli)' },
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
catch (err) {
|
|
62
|
+
throw new CommandExecutionError(`hf spaces request failed: ${err?.message ?? err}`);
|
|
63
|
+
}
|
|
64
|
+
if (resp.status === 429) {
|
|
65
|
+
throw new CommandExecutionError(
|
|
66
|
+
'hf spaces returned HTTP 429 (rate limited)',
|
|
67
|
+
'Hugging Face throttles unauthenticated traffic; wait a few seconds and retry.',
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
if (!resp.ok) {
|
|
71
|
+
throw new CommandExecutionError(`hf spaces failed: HTTP ${resp.status}`);
|
|
72
|
+
}
|
|
73
|
+
let data;
|
|
74
|
+
try {
|
|
75
|
+
data = await resp.json();
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
throw new CommandExecutionError(`hf spaces returned malformed JSON: ${err?.message ?? err}`);
|
|
79
|
+
}
|
|
80
|
+
const list = Array.isArray(data) ? data : [];
|
|
81
|
+
if (!list.length) {
|
|
82
|
+
throw new EmptyResultError('hf spaces', 'No matching spaces on huggingface.co.');
|
|
83
|
+
}
|
|
84
|
+
return list.slice(0, limit).map((s, i) => {
|
|
85
|
+
const id = String(s.id ?? s._id ?? '');
|
|
86
|
+
const slash = id.indexOf('/');
|
|
87
|
+
const author = String(s.author ?? (slash > 0 ? id.slice(0, slash) : ''));
|
|
88
|
+
const tags = Array.isArray(s.tags) ? s.tags.filter((t) => !String(t).startsWith('license:')).slice(0, 10).join(', ') : '';
|
|
89
|
+
return {
|
|
90
|
+
rank: i + 1,
|
|
91
|
+
id,
|
|
92
|
+
author,
|
|
93
|
+
sdk: String(s.sdk ?? ''),
|
|
94
|
+
likes: s.likes != null ? Number(s.likes) : null,
|
|
95
|
+
tags,
|
|
96
|
+
lastModified: String(s.lastModified ?? '').slice(0, 10),
|
|
97
|
+
url: id ? `https://huggingface.co/spaces/${id}` : '',
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
},
|
|
101
|
+
});
|
package/clis/hf/top.js
CHANGED
|
@@ -42,6 +42,7 @@ cli({
|
|
|
42
42
|
{ name: 'date', type: 'str', required: false, help: 'Date (YYYY-MM-DD), defaults to most recent' },
|
|
43
43
|
{ name: 'period', type: 'str', default: 'daily', choices: ['daily', 'weekly', 'monthly'], help: 'Time period: daily, weekly, or monthly' },
|
|
44
44
|
],
|
|
45
|
+
columns: ['rank', 'id', 'title', 'upvotes', 'authors'],
|
|
45
46
|
footerExtra: (kwargs) => {
|
|
46
47
|
if (kwargs._footerDate)
|
|
47
48
|
return kwargs._footerDate;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// homebrew cask — fetch a single Homebrew cask's metadata.
|
|
2
|
+
//
|
|
3
|
+
// Hits `https://formulae.brew.sh/api/cask/<token>.json`. Returns one row for
|
|
4
|
+
// the macOS/.dmg-style package: canonical token, friendly name, version,
|
|
5
|
+
// homepage, deprecated / disabled flags, download URL.
|
|
6
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
7
|
+
import { BREW_BASE, brewFetch, requireToken } from './utils.js';
|
|
8
|
+
|
|
9
|
+
cli({
|
|
10
|
+
site: 'homebrew',
|
|
11
|
+
name: 'cask',
|
|
12
|
+
access: 'read',
|
|
13
|
+
description: 'Fetch a Homebrew cask\'s metadata (version, homepage, deprecation, download URL)',
|
|
14
|
+
domain: 'formulae.brew.sh',
|
|
15
|
+
strategy: Strategy.PUBLIC,
|
|
16
|
+
browser: false,
|
|
17
|
+
args: [
|
|
18
|
+
{ name: 'token', positional: true, required: true, help: 'Cask token (e.g. "firefox", "visual-studio-code", "google-chrome")' },
|
|
19
|
+
],
|
|
20
|
+
columns: ['cask', 'tap', 'name', 'version', 'description', 'homepage', 'deprecated', 'disabled', 'download', 'url'],
|
|
21
|
+
func: async (args) => {
|
|
22
|
+
const token = requireToken(args.token, 'token');
|
|
23
|
+
const url = `${BREW_BASE}/cask/${encodeURIComponent(token)}.json`;
|
|
24
|
+
const body = await brewFetch(url, 'homebrew cask');
|
|
25
|
+
const friendly = Array.isArray(body?.name) ? body.name.filter(Boolean).join(', ') : String(body?.name ?? '').trim();
|
|
26
|
+
return [{
|
|
27
|
+
cask: String(body?.token ?? token).trim(),
|
|
28
|
+
tap: String(body?.tap ?? '').trim(),
|
|
29
|
+
name: friendly,
|
|
30
|
+
version: String(body?.version ?? '').trim(),
|
|
31
|
+
description: String(body?.desc ?? '').trim(),
|
|
32
|
+
homepage: String(body?.homepage ?? '').trim(),
|
|
33
|
+
deprecated: Boolean(body?.deprecated),
|
|
34
|
+
disabled: Boolean(body?.disabled),
|
|
35
|
+
download: String(body?.url ?? '').trim(),
|
|
36
|
+
url: `https://formulae.brew.sh/cask/${encodeURIComponent(token)}`,
|
|
37
|
+
}];
|
|
38
|
+
},
|
|
39
|
+
});
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
// homebrew formula — fetch a single Homebrew core formula's metadata.
|
|
2
|
+
//
|
|
3
|
+
// Hits `https://formulae.brew.sh/api/formula/<name>.json`. Returns one row:
|
|
4
|
+
// canonical name, latest stable version, license, dependencies, deprecated /
|
|
5
|
+
// disabled flags, homepage, source tarball URL.
|
|
6
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
7
|
+
import { BREW_BASE, brewFetch, requireToken } from './utils.js';
|
|
8
|
+
|
|
9
|
+
cli({
|
|
10
|
+
site: 'homebrew',
|
|
11
|
+
name: 'formula',
|
|
12
|
+
access: 'read',
|
|
13
|
+
description: 'Fetch a Homebrew formula\'s metadata (version, license, deps, deprecation, source)',
|
|
14
|
+
domain: 'formulae.brew.sh',
|
|
15
|
+
strategy: Strategy.PUBLIC,
|
|
16
|
+
browser: false,
|
|
17
|
+
args: [
|
|
18
|
+
{ name: 'name', positional: true, required: true, help: 'Formula name (e.g. "wget", "gcc@13", "imagemagick")' },
|
|
19
|
+
],
|
|
20
|
+
columns: ['formula', 'tap', 'version', 'license', 'description', 'homepage', 'dependencies', 'deprecated', 'disabled', 'source', 'url'],
|
|
21
|
+
func: async (args) => {
|
|
22
|
+
const name = requireToken(args.name, 'formula');
|
|
23
|
+
const url = `${BREW_BASE}/formula/${encodeURIComponent(name)}.json`;
|
|
24
|
+
const body = await brewFetch(url, 'homebrew formula');
|
|
25
|
+
const deps = Array.isArray(body?.dependencies) ? body.dependencies.filter(Boolean) : [];
|
|
26
|
+
const stableUrl = String(body?.urls?.stable?.url ?? '').trim();
|
|
27
|
+
return [{
|
|
28
|
+
formula: String(body?.name ?? name).trim(),
|
|
29
|
+
tap: String(body?.tap ?? '').trim(),
|
|
30
|
+
version: String(body?.versions?.stable ?? '').trim(),
|
|
31
|
+
license: String(body?.license ?? '').trim(),
|
|
32
|
+
description: String(body?.desc ?? '').trim(),
|
|
33
|
+
homepage: String(body?.homepage ?? '').trim(),
|
|
34
|
+
dependencies: deps.join(', '),
|
|
35
|
+
deprecated: Boolean(body?.deprecated),
|
|
36
|
+
disabled: Boolean(body?.disabled),
|
|
37
|
+
source: stableUrl,
|
|
38
|
+
url: `https://formulae.brew.sh/formula/${encodeURIComponent(name)}`,
|
|
39
|
+
}];
|
|
40
|
+
},
|
|
41
|
+
});
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
// homebrew popular — list the most-installed Homebrew formulae or casks.
|
|
2
|
+
//
|
|
3
|
+
// Hits `https://formulae.brew.sh/api/analytics/(install|cask-install)/<window>.json`.
|
|
4
|
+
// Anonymous-aggregated install counts published by Homebrew themselves;
|
|
5
|
+
// rows round-trip into `homebrew formula` / `homebrew cask` via the `token`
|
|
6
|
+
// column. The 30/90/365-day windows are the only ones the analytics endpoint
|
|
7
|
+
// publishes — anything else 404s upstream.
|
|
8
|
+
import { cli, Strategy } from '@jackwener/opencli/registry';
|
|
9
|
+
import { EmptyResultError } from '@jackwener/opencli/errors';
|
|
10
|
+
import { BREW_BASE, brewFetch, parseInstallCount, requireBoundedInt, requireOneOf } from './utils.js';
|
|
11
|
+
|
|
12
|
+
const TYPES = ['formula', 'cask'];
|
|
13
|
+
const WINDOWS = ['30d', '90d', '365d'];
|
|
14
|
+
|
|
15
|
+
cli({
|
|
16
|
+
site: 'homebrew',
|
|
17
|
+
name: 'popular',
|
|
18
|
+
access: 'read',
|
|
19
|
+
description: 'List most-installed Homebrew formulae or casks (Homebrew\'s analytics ranking)',
|
|
20
|
+
domain: 'formulae.brew.sh',
|
|
21
|
+
strategy: Strategy.PUBLIC,
|
|
22
|
+
browser: false,
|
|
23
|
+
args: [
|
|
24
|
+
{ name: 'type', default: 'formula', help: `Package type (${TYPES.join(' / ')})` },
|
|
25
|
+
{ name: 'window', default: '30d', help: `Time window (${WINDOWS.join(' / ')})` },
|
|
26
|
+
{ name: 'limit', type: 'int', default: 30, help: 'Max rows (1-500)' },
|
|
27
|
+
],
|
|
28
|
+
columns: ['rank', 'token', 'type', 'installs', 'percent', 'window', 'url'],
|
|
29
|
+
func: async (args) => {
|
|
30
|
+
const type = requireOneOf(args.type, TYPES, 'type');
|
|
31
|
+
const window = requireOneOf(args.window, WINDOWS, 'window');
|
|
32
|
+
const limit = requireBoundedInt(args.limit, 30, 500);
|
|
33
|
+
const path = type === 'cask' ? 'cask-install' : 'install';
|
|
34
|
+
const url = `${BREW_BASE}/analytics/${path}/${window}.json`;
|
|
35
|
+
const body = await brewFetch(url, 'homebrew popular');
|
|
36
|
+
const items = Array.isArray(body?.items) ? body.items : [];
|
|
37
|
+
if (!items.length) {
|
|
38
|
+
throw new EmptyResultError('homebrew popular', `Homebrew analytics returned no items for ${type}/${window}.`);
|
|
39
|
+
}
|
|
40
|
+
return items.slice(0, limit).map((row, i) => {
|
|
41
|
+
const token = String(type === 'cask' ? row.cask : row.formula ?? '').trim();
|
|
42
|
+
const detailPath = type === 'cask' ? 'cask' : 'formula';
|
|
43
|
+
return {
|
|
44
|
+
rank: row.number != null ? Number(row.number) : i + 1,
|
|
45
|
+
token,
|
|
46
|
+
type,
|
|
47
|
+
installs: parseInstallCount(row.count),
|
|
48
|
+
percent: row.percent != null ? Number(row.percent) : null,
|
|
49
|
+
window,
|
|
50
|
+
url: token ? `https://formulae.brew.sh/${detailPath}/${encodeURIComponent(token)}` : '',
|
|
51
|
+
};
|
|
52
|
+
});
|
|
53
|
+
},
|
|
54
|
+
});
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Shared helpers for the Homebrew adapters.
|
|
2
|
+
//
|
|
3
|
+
// Hits the public, unauthenticated `formulae.brew.sh/api` JSON endpoints
|
|
4
|
+
// (served as static files from GitHub Pages, regenerated daily). No auth.
|
|
5
|
+
// Formula / cask tokens are lowercase ASCII + `-_.+@` per Homebrew's own
|
|
6
|
+
// validation; they round-trip into `homebrew formula` / `homebrew cask`.
|
|
7
|
+
import { ArgumentError, CommandExecutionError, EmptyResultError } from '@jackwener/opencli/errors';
|
|
8
|
+
|
|
9
|
+
export const BREW_BASE = 'https://formulae.brew.sh/api';
|
|
10
|
+
const UA = 'opencli-homebrew-adapter (+https://github.com/jackwener/opencli)';
|
|
11
|
+
|
|
12
|
+
// Homebrew formula / cask tokens — letters / digits / `_-.+@` (`gcc@13`,
|
|
13
|
+
// `imagemagick@6`, `c++`, `0-ad`, `php-cs-fixer`).
|
|
14
|
+
const TOKEN = /^[A-Za-z0-9][A-Za-z0-9._+@-]*$/;
|
|
15
|
+
|
|
16
|
+
export function requireString(value, label) {
|
|
17
|
+
const s = String(value ?? '').trim();
|
|
18
|
+
if (!s) throw new ArgumentError(`homebrew ${label} cannot be empty`);
|
|
19
|
+
return s;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function requireBoundedInt(value, defaultValue, maxValue, label = 'limit') {
|
|
23
|
+
const raw = value ?? defaultValue;
|
|
24
|
+
const n = typeof raw === 'number' ? raw : Number(raw);
|
|
25
|
+
if (!Number.isInteger(n) || n <= 0) {
|
|
26
|
+
throw new ArgumentError(`homebrew ${label} must be a positive integer`);
|
|
27
|
+
}
|
|
28
|
+
if (n > maxValue) {
|
|
29
|
+
throw new ArgumentError(`homebrew ${label} must be <= ${maxValue}`);
|
|
30
|
+
}
|
|
31
|
+
return n;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function requireToken(value, label) {
|
|
35
|
+
const s = String(value ?? '').trim();
|
|
36
|
+
if (!s) {
|
|
37
|
+
throw new ArgumentError(`homebrew ${label} is required (e.g. "wget", "gcc@13", "firefox")`);
|
|
38
|
+
}
|
|
39
|
+
if (s.length > 100 || !TOKEN.test(s)) {
|
|
40
|
+
throw new ArgumentError(
|
|
41
|
+
`homebrew ${label} "${value}" is not a valid token`,
|
|
42
|
+
'Use letters / digits / "_-.+@", starting with a letter or digit (max 100 chars).',
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
return s;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function requireOneOf(value, allowed, label) {
|
|
49
|
+
const s = String(value ?? '').trim().toLowerCase();
|
|
50
|
+
if (!s) throw new ArgumentError(`homebrew ${label} is required`);
|
|
51
|
+
if (!allowed.includes(s)) {
|
|
52
|
+
throw new ArgumentError(
|
|
53
|
+
`homebrew ${label} "${value}" is not supported`,
|
|
54
|
+
`Allowed: ${allowed.join(', ')}.`,
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return s;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export async function brewFetch(url, label) {
|
|
61
|
+
let resp;
|
|
62
|
+
try {
|
|
63
|
+
resp = await fetch(url, { headers: { 'user-agent': UA, accept: 'application/json' } });
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
throw new CommandExecutionError(
|
|
67
|
+
`${label} request failed: ${err?.message ?? err}`,
|
|
68
|
+
'Check that formulae.brew.sh is reachable from this network.',
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
if (resp.status === 404) {
|
|
72
|
+
throw new EmptyResultError(label, `Homebrew API returned 404 for ${url}.`);
|
|
73
|
+
}
|
|
74
|
+
if (resp.status === 429) {
|
|
75
|
+
throw new CommandExecutionError(
|
|
76
|
+
`${label} returned HTTP 429 (rate limited)`,
|
|
77
|
+
'Homebrew throttles bursts; wait a few seconds and retry.',
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
if (!resp.ok) {
|
|
81
|
+
throw new CommandExecutionError(`${label} returned HTTP ${resp.status}`);
|
|
82
|
+
}
|
|
83
|
+
let body;
|
|
84
|
+
try {
|
|
85
|
+
body = await resp.json();
|
|
86
|
+
}
|
|
87
|
+
catch (err) {
|
|
88
|
+
throw new CommandExecutionError(`${label} returned malformed JSON: ${err?.message ?? err}`);
|
|
89
|
+
}
|
|
90
|
+
return body;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** Coerce a count value (which Homebrew analytics serves as `"139,972"`) to a plain number. */
|
|
94
|
+
export function parseInstallCount(value) {
|
|
95
|
+
if (value == null) return null;
|
|
96
|
+
const s = String(value).replace(/,/g, '').trim();
|
|
97
|
+
if (!s) return null;
|
|
98
|
+
const n = Number(s);
|
|
99
|
+
return Number.isFinite(n) ? n : null;
|
|
100
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="zh-CN"><head><title>虎扑社区</title></head><body>
|
|
3
|
+
<div id="container"><div class="bbs-index-web">
|
|
4
|
+
<nav>
|
|
5
|
+
<a href="/639999999.html"><span class="t-title">导航里的 thread-shaped phantom 链接</span></a>
|
|
6
|
+
</nav>
|
|
7
|
+
<div class="text-list-model">
|
|
8
|
+
<div class="list-item-wrap"><div class="list-title">步行街</div></div>
|
|
9
|
+
|
|
10
|
+
<div class="list-item-wrap"><div class="list-wrap"><div class="list-item">
|
|
11
|
+
<div class="t-info">
|
|
12
|
+
<a href="/639088523.html" target="_blank" class=" hot"><span class="t-title">网友曝三亚4只皮皮虾收费1035,官方凌晨通报</span></a>
|
|
13
|
+
<span class="t-lights">50亮</span>
|
|
14
|
+
<span class="t-replies">359回复</span>
|
|
15
|
+
</div>
|
|
16
|
+
<div class="t-label"><a href="/forum">步行街主干道</a></div>
|
|
17
|
+
</div></div></div>
|
|
18
|
+
|
|
19
|
+
<div class="list-item-wrap"><div class="list-wrap"><div class="list-item">
|
|
20
|
+
<div class="t-info">
|
|
21
|
+
<a href="/639095236.html" target="_blank" class=" false"><span class="t-title">[流言板]市监局称4只皮皮虾1035元价格合规</span></a>
|
|
22
|
+
<span class="t-lights">48亮</span>
|
|
23
|
+
<span class="t-replies">128回复</span>
|
|
24
|
+
</div>
|
|
25
|
+
<div class="t-label"><a href="/forum">步行街主干道</a></div>
|
|
26
|
+
</div></div></div>
|
|
27
|
+
|
|
28
|
+
<div class="list-item-wrap"><div class="list-wrap"><div class="list-item">
|
|
29
|
+
<div class="t-info">
|
|
30
|
+
<a href="/639094866.html" target="_blank" class=" false"><span class="t-title">印度顶层婆罗门家庭的微奢晚餐,大家觉得怎么样?</span></a>
|
|
31
|
+
<span class="t-lights">1.2万亮</span>
|
|
32
|
+
<span class="t-replies">5.8万回复</span>
|
|
33
|
+
</div>
|
|
34
|
+
<div class="t-label"><a href="/forum">步行街主干道</a></div>
|
|
35
|
+
</div></div></div>
|
|
36
|
+
|
|
37
|
+
<div class="list-item-wrap"><div class="list-wrap"><div class="list-item">
|
|
38
|
+
<div class="t-info">
|
|
39
|
+
<a href="/639100001.html" target="_blank" class=" hot"><span class="t-title">湖人战胜勇士拿下连胜</span></a>
|
|
40
|
+
<span class="t-lights">0亮</span>
|
|
41
|
+
<span class="t-replies">0回复</span>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="t-label"><a href="/forum">NBA湿乎乎的话题</a></div>
|
|
44
|
+
</div></div></div>
|
|
45
|
+
|
|
46
|
+
<div class="list-item-wrap"><div class="list-wrap"><div class="list-item">
|
|
47
|
+
<div class="t-info">
|
|
48
|
+
<a href="/639100002.html" target="_blank" class=" false"><span class="t-title">无 lights 的标题(少见但要兼容)</span></a>
|
|
49
|
+
|
|
50
|
+
<span class="t-replies">20回复</span>
|
|
51
|
+
</div>
|
|
52
|
+
<div class="t-label"><a href="/forum">电竞</a></div>
|
|
53
|
+
</div></div></div>
|
|
54
|
+
|
|
55
|
+
<div class="list-item-wrap"><div class="list-wrap"><div class="list-item">
|
|
56
|
+
<div class="t-info">
|
|
57
|
+
<a href="/639100003.html" target="_blank" class=" hot"><span class="t-title">另一个 hot 帖子</span></a>
|
|
58
|
+
<span class="t-lights">100亮</span>
|
|
59
|
+
<span class="t-replies">200回复</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="t-label"><a href="/forum">国际足球</a></div>
|
|
62
|
+
</div></div></div>
|
|
63
|
+
</div>
|
|
64
|
+
</div></div></body></html>
|