@jackwener/opencli 1.3.3 → 1.4.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/.github/pull_request_template.md +3 -1
- package/.github/workflows/build-extension.yml +7 -1
- package/.github/workflows/ci.yml +29 -3
- package/.github/workflows/docs.yml +1 -1
- package/.github/workflows/e2e-headed.yml +20 -0
- package/.github/workflows/release.yml +1 -1
- package/.github/workflows/security.yml +0 -3
- package/CHANGELOG.md +55 -0
- package/CONTRIBUTING.md +6 -3
- package/README.md +30 -3
- package/README.zh-CN.md +30 -3
- package/SKILL.md +7 -1
- package/TESTING.md +1 -0
- package/chatwise-opencli.ps1 +82 -0
- package/dist/analysis.d.ts +38 -0
- package/dist/analysis.js +166 -0
- package/dist/browser/cdp.d.ts +0 -4
- package/dist/browser/cdp.js +53 -41
- package/dist/browser/cdp.test.d.ts +1 -0
- package/dist/browser/cdp.test.js +52 -0
- package/dist/browser/dom-snapshot.d.ts +2 -2
- package/dist/browser/dom-snapshot.js +54 -1
- package/dist/browser/dom-snapshot.test.js +36 -0
- package/dist/browser/index.d.ts +2 -2
- package/dist/browser/index.js +1 -1
- package/dist/browser/mcp.d.ts +0 -2
- package/dist/browser/mcp.js +2 -3
- package/dist/browser/page.d.ts +4 -3
- package/dist/browser/page.js +34 -37
- package/dist/browser/stealth.d.ts +0 -2
- package/dist/browser/stealth.js +24 -9
- package/dist/browser.test.js +2 -2
- package/dist/build-manifest.js +15 -9
- package/dist/build-manifest.test.js +12 -0
- package/dist/cascade.js +4 -2
- package/dist/cli-manifest.json +639 -258
- package/dist/cli.js +57 -29
- package/dist/clis/_shared/desktop-commands.d.ts +22 -0
- package/dist/clis/_shared/desktop-commands.js +108 -0
- package/dist/clis/antigravity/serve.js +5 -2
- package/dist/clis/arxiv/search.js +1 -1
- package/dist/clis/bilibili/dynamic.test.d.ts +1 -0
- package/dist/clis/bilibili/dynamic.test.js +68 -0
- package/dist/clis/bilibili/favorite.js +4 -2
- package/dist/clis/bilibili/following.js +3 -2
- package/dist/clis/bilibili/subtitle.js +8 -7
- package/dist/clis/bilibili/utils.js +2 -2
- package/dist/clis/boss/batchgreet.js +1 -1
- package/dist/clis/boss/chatlist.js +1 -1
- package/dist/clis/boss/chatmsg.js +1 -1
- package/dist/clis/boss/detail.js +1 -1
- package/dist/clis/boss/exchange.js +1 -1
- package/dist/clis/boss/greet.js +1 -1
- package/dist/clis/boss/invite.js +1 -1
- package/dist/clis/boss/joblist.js +1 -1
- package/dist/clis/boss/mark.js +4 -3
- package/dist/clis/boss/recommend.js +1 -1
- package/dist/clis/boss/resume.js +1 -1
- package/dist/clis/boss/search.js +1 -1
- package/dist/clis/boss/send.js +5 -4
- package/dist/clis/boss/stats.js +1 -1
- package/dist/clis/chatgpt/ask.js +4 -0
- package/dist/clis/chatgpt/new.js +5 -1
- package/dist/clis/chatgpt/read.js +5 -1
- package/dist/clis/chatgpt/send.js +2 -1
- package/dist/clis/chatgpt/status.js +5 -1
- package/dist/clis/chatwise/ask.js +8 -2
- package/dist/clis/chatwise/export.js +2 -0
- package/dist/clis/chatwise/history.js +2 -0
- package/dist/clis/chatwise/model.js +8 -3
- package/dist/clis/chatwise/new.js +3 -18
- package/dist/clis/chatwise/read.js +2 -0
- package/dist/clis/chatwise/screenshot.js +3 -27
- package/dist/clis/chatwise/send.js +8 -2
- package/dist/clis/chatwise/shared.d.ts +2 -0
- package/dist/clis/chatwise/shared.js +6 -0
- package/dist/clis/chatwise/status.js +3 -22
- package/dist/clis/codex/ask.js +6 -2
- package/dist/clis/codex/dump.js +2 -25
- package/dist/clis/codex/new.js +2 -25
- package/dist/clis/codex/screenshot.js +2 -27
- package/dist/clis/codex/send.js +6 -4
- package/dist/clis/codex/status.js +2 -22
- package/dist/clis/cursor/ask.js +2 -1
- package/dist/clis/cursor/composer.js +2 -1
- package/dist/clis/cursor/dump.js +2 -25
- package/dist/clis/cursor/new.js +2 -18
- package/dist/clis/cursor/read.js +2 -1
- package/dist/clis/cursor/screenshot.js +1 -30
- package/dist/clis/cursor/send.js +2 -1
- package/dist/clis/cursor/status.js +2 -21
- package/dist/clis/dictionary/examples.yaml +25 -0
- package/dist/clis/dictionary/search.yaml +27 -0
- package/dist/clis/dictionary/synonyms.yaml +25 -0
- package/dist/clis/douban/book-hot.js +1 -1
- package/dist/clis/douban/movie-hot.js +1 -1
- package/dist/clis/douban/search.js +1 -1
- package/dist/clis/douban/utils.d.ts +4 -1
- package/dist/clis/douban/utils.js +156 -1
- package/dist/clis/doubao/ask.js +1 -1
- package/dist/clis/doubao/new.js +1 -1
- package/dist/clis/doubao/read.js +1 -1
- package/dist/clis/doubao/send.js +1 -1
- package/dist/clis/doubao/status.js +1 -1
- package/dist/clis/doubao-app/ask.js +1 -1
- package/dist/clis/doubao-app/new.js +1 -1
- package/dist/clis/doubao-app/read.js +1 -1
- package/dist/clis/doubao-app/send.js +1 -1
- package/dist/clis/grok/ask.d.ts +4 -0
- package/dist/clis/grok/ask.js +28 -10
- package/dist/clis/grok/ask.test.js +18 -0
- package/dist/clis/jd/item.d.ts +1 -0
- package/dist/clis/jd/item.js +96 -0
- package/dist/clis/jd/item.test.d.ts +1 -0
- package/dist/clis/jd/item.test.js +28 -0
- package/dist/clis/jike/feed.js +1 -1
- package/dist/clis/jike/search.js +1 -1
- package/dist/clis/linkedin/search.js +5 -4
- package/dist/clis/linkedin/timeline.d.ts +21 -0
- package/dist/clis/linkedin/timeline.js +503 -0
- package/dist/clis/linkedin/timeline.test.d.ts +1 -0
- package/dist/clis/linkedin/timeline.test.js +81 -0
- package/dist/clis/medium/feed.js +1 -1
- package/dist/clis/medium/search.js +1 -1
- package/dist/clis/medium/user.js +1 -1
- package/dist/clis/medium/{shared.js → utils.js} +2 -1
- package/dist/clis/pixiv/detail.yaml +49 -0
- package/dist/clis/pixiv/download.d.ts +7 -0
- package/dist/clis/pixiv/download.js +78 -0
- package/dist/clis/pixiv/download.test.d.ts +1 -0
- package/dist/clis/pixiv/download.test.js +87 -0
- package/dist/clis/pixiv/illusts.d.ts +8 -0
- package/dist/clis/pixiv/illusts.js +65 -0
- package/dist/clis/pixiv/illusts.test.d.ts +1 -0
- package/dist/clis/pixiv/illusts.test.js +99 -0
- package/dist/clis/pixiv/ranking.yaml +53 -0
- package/dist/clis/pixiv/search.d.ts +6 -0
- package/dist/clis/pixiv/search.js +43 -0
- package/dist/clis/pixiv/search.test.d.ts +1 -0
- package/dist/clis/pixiv/search.test.js +83 -0
- package/dist/clis/pixiv/test-utils.d.ts +12 -0
- package/dist/clis/pixiv/test-utils.js +23 -0
- package/dist/clis/pixiv/user.yaml +46 -0
- package/dist/clis/pixiv/utils.d.ts +27 -0
- package/dist/clis/pixiv/utils.js +49 -0
- package/dist/clis/reddit/comment.js +2 -1
- package/dist/clis/reddit/read.js +4 -3
- package/dist/clis/reddit/read.test.d.ts +1 -0
- package/dist/clis/reddit/read.test.js +28 -0
- package/dist/clis/reddit/save.js +2 -1
- package/dist/clis/reddit/saved.js +7 -3
- package/dist/clis/reddit/subscribe.js +2 -1
- package/dist/clis/reddit/upvote.js +2 -1
- package/dist/clis/reddit/upvoted.js +7 -3
- package/dist/clis/sinablog/article.js +1 -1
- package/dist/clis/sinablog/hot.js +1 -1
- package/dist/clis/sinablog/user.js +1 -1
- package/dist/clis/substack/feed.js +1 -1
- package/dist/clis/substack/publication.js +1 -1
- package/dist/clis/substack/search.js +3 -2
- package/dist/clis/substack/{shared.js → utils.js} +3 -2
- package/dist/clis/tiktok/search.yaml +2 -1
- package/dist/clis/twitter/accept.js +2 -1
- package/dist/clis/twitter/article.js +4 -1
- package/dist/clis/twitter/block.js +2 -1
- package/dist/clis/twitter/bookmark.js +2 -1
- package/dist/clis/twitter/bookmarks.js +3 -2
- package/dist/clis/twitter/delete.js +2 -1
- package/dist/clis/twitter/follow.js +2 -1
- package/dist/clis/twitter/followers.js +3 -2
- package/dist/clis/twitter/following.js +3 -2
- package/dist/clis/twitter/hide-reply.js +2 -1
- package/dist/clis/twitter/like.js +2 -1
- package/dist/clis/twitter/notifications.js +2 -1
- package/dist/clis/twitter/post.js +2 -1
- package/dist/clis/twitter/profile.js +5 -2
- package/dist/clis/twitter/reply-dm.js +2 -1
- package/dist/clis/twitter/reply.js +2 -1
- package/dist/clis/twitter/search.js +30 -13
- package/dist/clis/twitter/search.test.d.ts +1 -0
- package/dist/clis/twitter/search.test.js +104 -0
- package/dist/clis/twitter/thread.js +2 -2
- package/dist/clis/twitter/timeline.js +3 -2
- package/dist/clis/twitter/trending.js +3 -2
- package/dist/clis/twitter/unblock.js +2 -1
- package/dist/clis/twitter/unbookmark.js +2 -1
- package/dist/clis/twitter/unfollow.js +2 -1
- package/dist/clis/v2ex/daily.js +3 -2
- package/dist/clis/v2ex/me.js +3 -2
- package/dist/clis/v2ex/notifications.js +4 -4
- package/dist/clis/web/read.d.ts +16 -0
- package/dist/clis/web/read.js +202 -0
- package/dist/clis/xueqiu/danjuan-utils.d.ts +55 -0
- package/dist/clis/xueqiu/danjuan-utils.js +126 -0
- package/dist/clis/xueqiu/danjuan-utils.test.d.ts +1 -0
- package/dist/clis/xueqiu/danjuan-utils.test.js +41 -0
- package/dist/clis/xueqiu/fund-holdings.d.ts +1 -0
- package/dist/clis/xueqiu/fund-holdings.js +28 -0
- package/dist/clis/xueqiu/fund-snapshot.d.ts +1 -0
- package/dist/clis/xueqiu/fund-snapshot.js +25 -0
- package/dist/clis/youtube/transcript.js +5 -4
- package/dist/clis/youtube/video.js +3 -2
- package/dist/daemon.js +7 -3
- package/dist/discovery.js +11 -10
- package/dist/doctor.js +2 -1
- package/dist/download/index.d.ts +4 -12
- package/dist/download/index.js +33 -12
- package/dist/download/index.test.js +79 -2
- package/dist/download/media-download.js +4 -2
- package/dist/engine.test.js +76 -4
- package/dist/execution.d.ts +1 -9
- package/dist/execution.js +56 -46
- package/dist/explore.js +12 -111
- package/dist/external-clis.yaml +0 -8
- package/dist/external.js +7 -5
- package/dist/external.test.js +4 -0
- package/dist/generate.d.ts +0 -9
- package/dist/generate.js +4 -20
- package/dist/hooks.d.ts +46 -0
- package/dist/hooks.js +56 -0
- package/dist/hooks.test.d.ts +4 -0
- package/dist/hooks.test.js +92 -0
- package/dist/interceptor.js +70 -23
- package/dist/main.js +2 -0
- package/dist/output.js +12 -6
- package/dist/pipeline/executor.js +1 -1
- package/dist/pipeline/steps/browser.js +1 -3
- package/dist/pipeline/steps/download.js +42 -26
- package/dist/pipeline/steps/download.test.d.ts +1 -0
- package/dist/pipeline/steps/download.test.js +101 -0
- package/dist/pipeline/steps/fetch.js +40 -22
- package/dist/pipeline/steps/fetch.test.d.ts +1 -0
- package/dist/pipeline/steps/fetch.test.js +123 -0
- package/dist/pipeline/steps/transform.js +2 -6
- package/dist/pipeline/template.js +66 -52
- package/dist/pipeline/template.test.js +28 -0
- package/dist/pipeline/transform.test.js +18 -0
- package/dist/plugin.d.ts +40 -1
- package/dist/plugin.js +214 -17
- package/dist/plugin.test.d.ts +1 -1
- package/dist/plugin.test.js +219 -3
- package/dist/record.js +6 -98
- package/dist/registry-api.d.ts +2 -0
- package/dist/registry-api.js +1 -0
- package/dist/registry.d.ts +5 -2
- package/dist/registry.js +1 -2
- package/dist/runtime.d.ts +0 -1
- package/dist/runtime.js +14 -4
- package/dist/snapshotFormatter.d.ts +7 -14
- package/dist/snapshotFormatter.js +38 -78
- package/dist/utils.d.ts +9 -0
- package/dist/utils.js +29 -0
- package/dist/validate.js +3 -5
- package/dist/yaml-schema.d.ts +26 -0
- package/dist/yaml-schema.js +5 -0
- package/docs/.vitepress/config.mts +3 -0
- package/docs/adapters/browser/dictionary.md +27 -0
- package/docs/adapters/browser/jd.md +27 -0
- package/docs/adapters/browser/linkedin.md +6 -0
- package/docs/adapters/browser/pixiv.md +92 -0
- package/docs/adapters/browser/web.md +30 -0
- package/docs/adapters/browser/xueqiu.md +27 -9
- package/docs/adapters/index.md +3 -1
- package/docs/comparison.md +125 -0
- package/docs/developer/contributing.md +21 -2
- package/docs/developer/testing.md +14 -8
- package/docs/developer/ts-adapter.md +18 -0
- package/docs/developer/yaml-adapter.md +16 -0
- package/docs/guide/plugins.md +10 -0
- package/docs/zh/guide/plugins.md +10 -0
- package/extension/dist/background.js +519 -444
- package/extension/manifest.json +1 -1
- package/extension/package.json +1 -1
- package/extension/src/background.test.ts +46 -1
- package/extension/src/background.ts +108 -33
- package/extension/src/cdp.ts +9 -9
- package/package.json +3 -2
- package/scripts/check-doc-coverage.sh +2 -0
- package/src/analysis.ts +170 -0
- package/src/browser/cdp.test.ts +66 -0
- package/src/browser/cdp.ts +59 -44
- package/src/browser/dom-snapshot.test.ts +42 -0
- package/src/browser/dom-snapshot.ts +56 -3
- package/src/browser/index.ts +2 -2
- package/src/browser/mcp.ts +2 -4
- package/src/browser/page.ts +34 -37
- package/src/browser/stealth.ts +24 -10
- package/src/browser.test.ts +2 -2
- package/src/build-manifest.test.ts +14 -0
- package/src/build-manifest.ts +13 -31
- package/src/cascade.ts +5 -3
- package/src/cli.ts +66 -34
- package/src/clis/_shared/desktop-commands.ts +121 -0
- package/src/clis/antigravity/serve.ts +6 -3
- package/src/clis/arxiv/search.ts +1 -1
- package/src/clis/bilibili/dynamic.test.ts +79 -0
- package/src/clis/bilibili/favorite.ts +5 -2
- package/src/clis/bilibili/following.ts +3 -2
- package/src/clis/bilibili/subtitle.ts +8 -7
- package/src/clis/bilibili/utils.ts +2 -2
- package/src/clis/boss/batchgreet.ts +1 -1
- package/src/clis/boss/chatlist.ts +1 -1
- package/src/clis/boss/chatmsg.ts +1 -1
- package/src/clis/boss/detail.ts +1 -1
- package/src/clis/boss/exchange.ts +1 -1
- package/src/clis/boss/greet.ts +1 -1
- package/src/clis/boss/invite.ts +1 -1
- package/src/clis/boss/joblist.ts +1 -1
- package/src/clis/boss/mark.ts +4 -3
- package/src/clis/boss/recommend.ts +1 -1
- package/src/clis/boss/resume.ts +1 -1
- package/src/clis/boss/search.ts +1 -1
- package/src/clis/boss/send.ts +5 -4
- package/src/clis/boss/stats.ts +1 -1
- package/src/clis/chatgpt/ask.ts +5 -0
- package/src/clis/chatgpt/new.ts +7 -2
- package/src/clis/chatgpt/read.ts +7 -2
- package/src/clis/chatgpt/send.ts +3 -2
- package/src/clis/chatgpt/status.ts +6 -1
- package/src/clis/chatwise/ask.ts +7 -2
- package/src/clis/chatwise/export.ts +2 -0
- package/src/clis/chatwise/history.ts +2 -0
- package/src/clis/chatwise/model.ts +7 -3
- package/src/clis/chatwise/new.ts +3 -20
- package/src/clis/chatwise/read.ts +2 -0
- package/src/clis/chatwise/screenshot.ts +3 -32
- package/src/clis/chatwise/send.ts +7 -2
- package/src/clis/chatwise/shared.ts +8 -0
- package/src/clis/chatwise/status.ts +3 -24
- package/src/clis/codex/ask.ts +5 -2
- package/src/clis/codex/dump.ts +2 -27
- package/src/clis/codex/new.ts +2 -28
- package/src/clis/codex/screenshot.ts +2 -32
- package/src/clis/codex/send.ts +5 -4
- package/src/clis/codex/status.ts +2 -24
- package/src/clis/cursor/ask.ts +2 -1
- package/src/clis/cursor/composer.ts +2 -1
- package/src/clis/cursor/dump.ts +2 -27
- package/src/clis/cursor/new.ts +2 -20
- package/src/clis/cursor/read.ts +2 -1
- package/src/clis/cursor/screenshot.ts +1 -36
- package/src/clis/cursor/send.ts +2 -1
- package/src/clis/cursor/status.ts +2 -22
- package/src/clis/dictionary/examples.yaml +25 -0
- package/src/clis/dictionary/search.yaml +27 -0
- package/src/clis/dictionary/synonyms.yaml +25 -0
- package/src/clis/douban/book-hot.ts +1 -1
- package/src/clis/douban/movie-hot.ts +1 -1
- package/src/clis/douban/search.ts +1 -1
- package/src/clis/douban/utils.ts +165 -1
- package/src/clis/doubao/ask.ts +1 -1
- package/src/clis/doubao/new.ts +1 -1
- package/src/clis/doubao/read.ts +1 -1
- package/src/clis/doubao/send.ts +1 -1
- package/src/clis/doubao/status.ts +1 -1
- package/src/clis/doubao-app/ask.ts +1 -1
- package/src/clis/doubao-app/new.ts +1 -1
- package/src/clis/doubao-app/read.ts +1 -1
- package/src/clis/doubao-app/send.ts +1 -1
- package/src/clis/grok/ask.test.ts +25 -0
- package/src/clis/grok/ask.ts +25 -12
- package/src/clis/jd/item.test.ts +35 -0
- package/src/clis/jd/item.ts +101 -0
- package/src/clis/jike/feed.ts +1 -1
- package/src/clis/jike/search.ts +1 -1
- package/src/clis/linkedin/search.ts +5 -4
- package/src/clis/linkedin/timeline.test.ts +99 -0
- package/src/clis/linkedin/timeline.ts +532 -0
- package/src/clis/medium/feed.ts +1 -1
- package/src/clis/medium/search.ts +1 -1
- package/src/clis/medium/user.ts +1 -1
- package/src/clis/medium/{shared.ts → utils.ts} +2 -1
- package/src/clis/pixiv/detail.yaml +49 -0
- package/src/clis/pixiv/download.test.ts +114 -0
- package/src/clis/pixiv/download.ts +91 -0
- package/src/clis/pixiv/illusts.test.ts +115 -0
- package/src/clis/pixiv/illusts.ts +78 -0
- package/src/clis/pixiv/ranking.yaml +53 -0
- package/src/clis/pixiv/search.test.ts +97 -0
- package/src/clis/pixiv/search.ts +53 -0
- package/src/clis/pixiv/test-utils.ts +29 -0
- package/src/clis/pixiv/user.yaml +46 -0
- package/src/clis/pixiv/utils.ts +62 -0
- package/src/clis/reddit/comment.ts +2 -1
- package/src/clis/reddit/read.test.ts +34 -0
- package/src/clis/reddit/read.ts +4 -3
- package/src/clis/reddit/save.ts +2 -1
- package/src/clis/reddit/saved.ts +6 -2
- package/src/clis/reddit/subscribe.ts +2 -1
- package/src/clis/reddit/upvote.ts +2 -1
- package/src/clis/reddit/upvoted.ts +6 -2
- package/src/clis/sinablog/article.ts +1 -1
- package/src/clis/sinablog/hot.ts +1 -1
- package/src/clis/sinablog/user.ts +1 -1
- package/src/clis/substack/feed.ts +1 -1
- package/src/clis/substack/publication.ts +1 -1
- package/src/clis/substack/search.ts +3 -2
- package/src/clis/substack/{shared.ts → utils.ts} +3 -2
- package/src/clis/tiktok/search.yaml +2 -1
- package/src/clis/twitter/accept.ts +2 -1
- package/src/clis/twitter/article.ts +3 -1
- package/src/clis/twitter/block.ts +2 -1
- package/src/clis/twitter/bookmark.ts +2 -1
- package/src/clis/twitter/bookmarks.ts +3 -2
- package/src/clis/twitter/delete.ts +2 -1
- package/src/clis/twitter/follow.ts +2 -1
- package/src/clis/twitter/followers.ts +3 -2
- package/src/clis/twitter/following.ts +3 -2
- package/src/clis/twitter/hide-reply.ts +2 -1
- package/src/clis/twitter/like.ts +2 -1
- package/src/clis/twitter/notifications.ts +2 -1
- package/src/clis/twitter/post.ts +2 -1
- package/src/clis/twitter/profile.ts +4 -2
- package/src/clis/twitter/reply-dm.ts +2 -1
- package/src/clis/twitter/reply.ts +2 -1
- package/src/clis/twitter/search.test.ts +113 -0
- package/src/clis/twitter/search.ts +38 -14
- package/src/clis/twitter/thread.ts +2 -2
- package/src/clis/twitter/timeline.ts +3 -2
- package/src/clis/twitter/trending.ts +3 -2
- package/src/clis/twitter/unblock.ts +2 -1
- package/src/clis/twitter/unbookmark.ts +2 -1
- package/src/clis/twitter/unfollow.ts +2 -1
- package/src/clis/v2ex/daily.ts +3 -2
- package/src/clis/v2ex/me.ts +3 -2
- package/src/clis/v2ex/notifications.ts +3 -4
- package/src/clis/web/read.ts +210 -0
- package/src/clis/xueqiu/danjuan-utils.test.ts +49 -0
- package/src/clis/xueqiu/danjuan-utils.ts +176 -0
- package/src/clis/xueqiu/fund-holdings.ts +32 -0
- package/src/clis/xueqiu/fund-snapshot.ts +27 -0
- package/src/clis/youtube/transcript.ts +5 -4
- package/src/clis/youtube/video.ts +3 -2
- package/src/daemon.ts +5 -4
- package/src/discovery.ts +12 -34
- package/src/doctor.ts +3 -2
- package/src/download/index.test.ts +93 -2
- package/src/download/index.ts +44 -23
- package/src/download/media-download.ts +5 -3
- package/src/engine.test.ts +84 -3
- package/src/execution.ts +62 -46
- package/src/explore.ts +21 -90
- package/src/external-clis.yaml +0 -8
- package/src/external.test.ts +9 -0
- package/src/external.ts +12 -10
- package/src/generate.ts +4 -41
- package/src/hooks.test.ts +126 -0
- package/src/hooks.ts +90 -0
- package/src/interceptor.ts +73 -23
- package/src/main.ts +2 -0
- package/src/output.ts +14 -6
- package/src/pipeline/executor.ts +1 -1
- package/src/pipeline/steps/browser.ts +1 -3
- package/src/pipeline/steps/download.test.ts +136 -0
- package/src/pipeline/steps/download.ts +47 -34
- package/src/pipeline/steps/fetch.test.ts +179 -0
- package/src/pipeline/steps/fetch.ts +39 -23
- package/src/pipeline/steps/transform.ts +2 -6
- package/src/pipeline/template.test.ts +28 -0
- package/src/pipeline/template.ts +67 -79
- package/src/pipeline/transform.test.ts +20 -0
- package/src/plugin.test.ts +251 -3
- package/src/plugin.ts +265 -21
- package/src/record.ts +12 -84
- package/src/registry-api.ts +2 -0
- package/src/registry.ts +7 -4
- package/src/runtime.ts +14 -4
- package/src/snapshotFormatter.ts +43 -121
- package/src/utils.ts +39 -0
- package/src/validate.ts +3 -5
- package/src/yaml-schema.ts +28 -0
- package/tests/e2e/browser-auth.test.ts +25 -0
- package/tests/e2e/plugin-management.test.ts +137 -0
- package/tests/e2e/public-commands.test.ts +34 -1
- package/vitest.config.ts +19 -1
- package/.github/workflows/pkg-pr-new.yml +0 -30
- package/dist/clis/douban/shared.d.ts +0 -4
- package/dist/clis/douban/shared.js +0 -155
- package/src/clis/douban/shared.ts +0 -165
- /package/dist/clis/boss/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/boss/{common.js → utils.js} +0 -0
- /package/dist/clis/doubao/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/doubao/{common.js → utils.js} +0 -0
- /package/dist/clis/doubao-app/{common.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/doubao-app/{common.js → utils.js} +0 -0
- /package/dist/clis/jike/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/jike/{shared.js → utils.js} +0 -0
- /package/dist/clis/medium/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/sinablog/{shared.d.ts → utils.d.ts} +0 -0
- /package/dist/clis/sinablog/{shared.js → utils.js} +0 -0
- /package/dist/clis/substack/{shared.d.ts → utils.d.ts} +0 -0
- /package/src/clis/boss/{common.ts → utils.ts} +0 -0
- /package/src/clis/doubao/{common.ts → utils.ts} +0 -0
- /package/src/clis/doubao-app/{common.ts → utils.ts} +0 -0
- /package/src/clis/jike/{shared.ts → utils.ts} +0 -0
- /package/src/clis/sinablog/{shared.ts → utils.ts} +0 -0
package/src/generate.ts
CHANGED
|
@@ -12,30 +12,13 @@ import { exploreUrl } from './explore.js';
|
|
|
12
12
|
import type { IBrowserFactory } from './runtime.js';
|
|
13
13
|
import { synthesizeFromExplore, type SynthesizeCandidateSummary, type SynthesizeResult } from './synthesize.js';
|
|
14
14
|
|
|
15
|
-
// Registration is a no-op stub — candidates are written to disk by synthesize,
|
|
16
|
-
// but not yet auto-copied into the user clis dir.
|
|
17
|
-
interface RegisterCandidatesOptions {
|
|
18
|
-
target: string;
|
|
19
|
-
builtinClis?: string;
|
|
20
|
-
userClis?: string;
|
|
21
|
-
name?: string;
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
interface RegisterCandidatesResult {
|
|
25
|
-
ok: boolean;
|
|
26
|
-
count: number;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
15
|
export interface GenerateCliOptions {
|
|
30
16
|
url: string;
|
|
31
17
|
BrowserFactory: new () => IBrowserFactory;
|
|
32
|
-
builtinClis?: string;
|
|
33
|
-
userClis?: string;
|
|
34
18
|
goal?: string | null;
|
|
35
19
|
site?: string;
|
|
36
20
|
waitSeconds?: number;
|
|
37
21
|
top?: number;
|
|
38
|
-
register?: boolean;
|
|
39
22
|
workspace?: string;
|
|
40
23
|
}
|
|
41
24
|
|
|
@@ -57,11 +40,6 @@ export interface GenerateCliResult {
|
|
|
57
40
|
candidate_count: number;
|
|
58
41
|
candidates: Array<Pick<SynthesizeCandidateSummary, 'name' | 'strategy' | 'confidence'>>;
|
|
59
42
|
};
|
|
60
|
-
register: RegisterCandidatesResult | null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
function registerCandidates(_opts: RegisterCandidatesOptions): RegisterCandidatesResult {
|
|
64
|
-
return { ok: true, count: 0 };
|
|
65
43
|
}
|
|
66
44
|
|
|
67
45
|
const CAPABILITY_ALIASES: Record<string, string[]> = {
|
|
@@ -102,9 +80,10 @@ function selectCandidate(candidates: SynthesizeResult['candidates'], goal?: stri
|
|
|
102
80
|
}
|
|
103
81
|
|
|
104
82
|
const lower = (goal ?? '').trim().toLowerCase();
|
|
105
|
-
const partial = candidates.find(c =>
|
|
106
|
-
c.name?.toLowerCase()
|
|
107
|
-
|
|
83
|
+
const partial = candidates.find(c => {
|
|
84
|
+
const cName = c.name?.toLowerCase() ?? '';
|
|
85
|
+
return cName.includes(lower) || lower.includes(cName);
|
|
86
|
+
});
|
|
108
87
|
return partial ?? candidates[0];
|
|
109
88
|
}
|
|
110
89
|
|
|
@@ -127,19 +106,6 @@ export async function generateCliFromUrl(opts: GenerateCliOptions): Promise<Gene
|
|
|
127
106
|
const selected = selectCandidate(synthesizeResult.candidates ?? [], opts.goal);
|
|
128
107
|
const selectedSite = synthesizeResult.site ?? exploreResult.site;
|
|
129
108
|
|
|
130
|
-
// Step 4: Register (if requested)
|
|
131
|
-
let registerResult: RegisterCandidatesResult | null = null;
|
|
132
|
-
if (opts.register !== false && synthesizeResult.candidate_count > 0) {
|
|
133
|
-
try {
|
|
134
|
-
registerResult = registerCandidates({
|
|
135
|
-
target: synthesizeResult.out_dir,
|
|
136
|
-
builtinClis: opts.builtinClis,
|
|
137
|
-
userClis: opts.userClis,
|
|
138
|
-
name: selected?.name,
|
|
139
|
-
});
|
|
140
|
-
} catch {}
|
|
141
|
-
}
|
|
142
|
-
|
|
143
109
|
const ok = exploreResult.endpoint_count > 0 && synthesizeResult.candidate_count > 0;
|
|
144
110
|
|
|
145
111
|
return {
|
|
@@ -164,7 +130,6 @@ export async function generateCliFromUrl(opts: GenerateCliOptions): Promise<Gene
|
|
|
164
130
|
confidence: c.confidence,
|
|
165
131
|
})),
|
|
166
132
|
},
|
|
167
|
-
register: registerResult,
|
|
168
133
|
};
|
|
169
134
|
}
|
|
170
135
|
|
|
@@ -188,8 +153,6 @@ export function renderGenerateSummary(r: GenerateCliResult): string {
|
|
|
188
153
|
lines.push(` • ${c.name} (${c.strategy}, ${((c.confidence ?? 0) * 100).toFixed(0)}%)`);
|
|
189
154
|
}
|
|
190
155
|
|
|
191
|
-
if (r.register) lines.push(`\nRegistered: ${r.register.count ?? 0}`);
|
|
192
|
-
|
|
193
156
|
const fw = r.explore?.framework ?? {};
|
|
194
157
|
const fwNames = Object.entries(fw).filter(([, v]) => v).map(([k]) => k);
|
|
195
158
|
if (fwNames.length) lines.push(`Framework: ${fwNames.join(', ')}`);
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests for the plugin lifecycle hooks system.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach } from 'vitest';
|
|
6
|
+
import {
|
|
7
|
+
onStartup,
|
|
8
|
+
onBeforeExecute,
|
|
9
|
+
onAfterExecute,
|
|
10
|
+
emitHook,
|
|
11
|
+
clearAllHooks,
|
|
12
|
+
type HookContext,
|
|
13
|
+
} from './hooks.js';
|
|
14
|
+
|
|
15
|
+
beforeEach(() => {
|
|
16
|
+
clearAllHooks();
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('hook registration and emission', () => {
|
|
20
|
+
it('onBeforeExecute hook is called with context', async () => {
|
|
21
|
+
const calls: HookContext[] = [];
|
|
22
|
+
onBeforeExecute((ctx) => { calls.push({ ...ctx }); });
|
|
23
|
+
|
|
24
|
+
await emitHook('onBeforeExecute', { command: 'test/cmd', args: { limit: 5 }, startedAt: 100 });
|
|
25
|
+
|
|
26
|
+
expect(calls).toHaveLength(1);
|
|
27
|
+
expect(calls[0].command).toBe('test/cmd');
|
|
28
|
+
expect(calls[0].args).toEqual({ limit: 5 });
|
|
29
|
+
expect(calls[0].startedAt).toBe(100);
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('onAfterExecute hook receives result', async () => {
|
|
33
|
+
const results: unknown[] = [];
|
|
34
|
+
onAfterExecute((_ctx, result) => { results.push(result); });
|
|
35
|
+
|
|
36
|
+
const mockResult = [{ title: 'item1' }, { title: 'item2' }];
|
|
37
|
+
await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, mockResult);
|
|
38
|
+
|
|
39
|
+
expect(results).toHaveLength(1);
|
|
40
|
+
expect(results[0]).toEqual(mockResult);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it('onStartup hook fires', async () => {
|
|
44
|
+
let fired = false;
|
|
45
|
+
onStartup(() => { fired = true; });
|
|
46
|
+
|
|
47
|
+
await emitHook('onStartup', { command: '__startup__', args: {} });
|
|
48
|
+
expect(fired).toBe(true);
|
|
49
|
+
});
|
|
50
|
+
|
|
51
|
+
it('multiple hooks on the same event fire in order', async () => {
|
|
52
|
+
const order: number[] = [];
|
|
53
|
+
onBeforeExecute(() => { order.push(1); });
|
|
54
|
+
onBeforeExecute(() => { order.push(2); });
|
|
55
|
+
onBeforeExecute(() => { order.push(3); });
|
|
56
|
+
|
|
57
|
+
await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
|
|
58
|
+
expect(order).toEqual([1, 2, 3]);
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('async hooks are awaited', async () => {
|
|
62
|
+
const order: string[] = [];
|
|
63
|
+
onBeforeExecute(async () => {
|
|
64
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
65
|
+
order.push('async-done');
|
|
66
|
+
});
|
|
67
|
+
onBeforeExecute(() => { order.push('sync'); });
|
|
68
|
+
|
|
69
|
+
await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
|
|
70
|
+
expect(order).toEqual(['async-done', 'sync']);
|
|
71
|
+
});
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('hook error isolation', () => {
|
|
75
|
+
it('failing hook does not prevent other hooks from running', async () => {
|
|
76
|
+
const calls: string[] = [];
|
|
77
|
+
|
|
78
|
+
onBeforeExecute(() => { calls.push('first'); });
|
|
79
|
+
onBeforeExecute(() => { throw new Error('boom'); });
|
|
80
|
+
onBeforeExecute(() => { calls.push('third'); });
|
|
81
|
+
|
|
82
|
+
await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
|
|
83
|
+
|
|
84
|
+
// First and third should still run despite the second throwing
|
|
85
|
+
expect(calls).toEqual(['first', 'third']);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
it('async hook rejection does not prevent other hooks', async () => {
|
|
89
|
+
const calls: string[] = [];
|
|
90
|
+
|
|
91
|
+
onAfterExecute(() => { calls.push('before-reject'); });
|
|
92
|
+
onAfterExecute(async () => { throw new Error('async boom'); });
|
|
93
|
+
onAfterExecute(() => { calls.push('after-reject'); });
|
|
94
|
+
|
|
95
|
+
await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, null);
|
|
96
|
+
|
|
97
|
+
expect(calls).toEqual(['before-reject', 'after-reject']);
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
describe('no-op when no hooks registered', () => {
|
|
102
|
+
it('emitHook with no registered hooks does nothing', async () => {
|
|
103
|
+
// Should not throw
|
|
104
|
+
await emitHook('onBeforeExecute', { command: 'test/cmd', args: {} });
|
|
105
|
+
await emitHook('onAfterExecute', { command: 'test/cmd', args: {} }, []);
|
|
106
|
+
await emitHook('onStartup', { command: '__startup__', args: {} });
|
|
107
|
+
});
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
describe('clearAllHooks', () => {
|
|
111
|
+
it('removes all hooks', async () => {
|
|
112
|
+
let called = false;
|
|
113
|
+
onStartup(() => { called = true; });
|
|
114
|
+
|
|
115
|
+
clearAllHooks();
|
|
116
|
+
await emitHook('onStartup', { command: '__startup__', args: {} });
|
|
117
|
+
|
|
118
|
+
expect(called).toBe(false);
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
describe('globalThis singleton', () => {
|
|
123
|
+
it('uses globalThis.__opencli_hooks__ for shared state', () => {
|
|
124
|
+
expect(globalThis.__opencli_hooks__).toBeInstanceOf(Map);
|
|
125
|
+
});
|
|
126
|
+
});
|
package/src/hooks.ts
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plugin lifecycle hooks: allows plugins to tap into opencli's execution lifecycle.
|
|
3
|
+
*
|
|
4
|
+
* Hooks use globalThis (like the command registry) to guarantee a single shared
|
|
5
|
+
* instance across all module copies — critical when TS plugins are loaded via
|
|
6
|
+
* npm link / peerDependency symlinks.
|
|
7
|
+
*
|
|
8
|
+
* Available hooks:
|
|
9
|
+
* onStartup — fired once after all commands & plugins are discovered
|
|
10
|
+
* onBeforeExecute — fired before every command execution
|
|
11
|
+
* onAfterExecute — fired after every command execution (receives result)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { log } from './logger.js';
|
|
15
|
+
|
|
16
|
+
export type HookName = 'onStartup' | 'onBeforeExecute' | 'onAfterExecute';
|
|
17
|
+
|
|
18
|
+
export interface HookContext {
|
|
19
|
+
/** Command full name in "site/name" format, or "__startup__" for onStartup */
|
|
20
|
+
command: string;
|
|
21
|
+
/** Coerced and validated arguments */
|
|
22
|
+
args: Record<string, unknown>;
|
|
23
|
+
/** Epoch ms when execution started (set by executeCommand) */
|
|
24
|
+
startedAt?: number;
|
|
25
|
+
/** Epoch ms when execution finished (set by executeCommand) */
|
|
26
|
+
finishedAt?: number;
|
|
27
|
+
/** Error thrown by the command, if execution failed */
|
|
28
|
+
error?: unknown;
|
|
29
|
+
/** Plugins can attach arbitrary data here for cross-hook communication */
|
|
30
|
+
[key: string]: unknown;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export type HookFn = (ctx: HookContext, result?: unknown) => void | Promise<void>;
|
|
34
|
+
|
|
35
|
+
// ── Singleton hook store (shared across module instances via globalThis) ──
|
|
36
|
+
declare global {
|
|
37
|
+
// eslint-disable-next-line no-var
|
|
38
|
+
var __opencli_hooks__: Map<HookName, HookFn[]> | undefined;
|
|
39
|
+
}
|
|
40
|
+
const _hooks: Map<HookName, HookFn[]> =
|
|
41
|
+
globalThis.__opencli_hooks__ ??= new Map();
|
|
42
|
+
|
|
43
|
+
// ── Registration API (used by plugins) ─────────────────────────────────────
|
|
44
|
+
|
|
45
|
+
function addHook(name: HookName, fn: HookFn): void {
|
|
46
|
+
const list = _hooks.get(name) ?? [];
|
|
47
|
+
list.push(fn);
|
|
48
|
+
_hooks.set(name, list);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Register a hook that fires once after all plugins are discovered. */
|
|
52
|
+
export function onStartup(fn: HookFn): void {
|
|
53
|
+
addHook('onStartup', fn);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** Register a hook that fires before every command execution. */
|
|
57
|
+
export function onBeforeExecute(fn: HookFn): void {
|
|
58
|
+
addHook('onBeforeExecute', fn);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Register a hook that fires after every command execution with the result. */
|
|
62
|
+
export function onAfterExecute(fn: HookFn): void {
|
|
63
|
+
addHook('onAfterExecute', fn);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// ── Emit API (used internally by opencli core) ─────────────────────────────
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Trigger all registered handlers for a hook.
|
|
70
|
+
* Each handler is wrapped in try/catch — a failing hook never blocks command execution.
|
|
71
|
+
*/
|
|
72
|
+
export async function emitHook(name: HookName, ctx: HookContext, result?: unknown): Promise<void> {
|
|
73
|
+
const handlers = _hooks.get(name);
|
|
74
|
+
if (!handlers || handlers.length === 0) return;
|
|
75
|
+
|
|
76
|
+
for (const fn of handlers) {
|
|
77
|
+
try {
|
|
78
|
+
await fn(ctx, result);
|
|
79
|
+
} catch (err) {
|
|
80
|
+
log.warn(`Hook ${name} handler failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Remove all registered hooks. Intended for testing only.
|
|
87
|
+
*/
|
|
88
|
+
export function clearAllHooks(): void {
|
|
89
|
+
_hooks.clear();
|
|
90
|
+
}
|
package/src/interceptor.ts
CHANGED
|
@@ -9,6 +9,44 @@
|
|
|
9
9
|
* - stepTap (pipeline/steps/tap.ts)
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
/**
|
|
13
|
+
* Helper: define a non-enumerable property on window.
|
|
14
|
+
* Avoids detection via Object.keys(window) or for..in loops.
|
|
15
|
+
*/
|
|
16
|
+
const DEFINE_HIDDEN = `
|
|
17
|
+
function __defHidden(obj, key, val) {
|
|
18
|
+
try {
|
|
19
|
+
Object.defineProperty(obj, key, { value: val, writable: true, enumerable: false, configurable: true });
|
|
20
|
+
} catch { obj[key] = val; }
|
|
21
|
+
}`;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Helper: disguise a patched function so toString() returns native code signature.
|
|
25
|
+
*/
|
|
26
|
+
const DISGUISE_FN = `
|
|
27
|
+
function __disguise(fn, name) {
|
|
28
|
+
const nativeStr = 'function ' + name + '() { [native code] }';
|
|
29
|
+
// Override toString on the instance AND patch Function.prototype.toString
|
|
30
|
+
// to handle Function.prototype.toString.call(fn) bypasses.
|
|
31
|
+
const _origToString = Function.prototype.toString;
|
|
32
|
+
const _patchedFns = window.__dFns || (function() {
|
|
33
|
+
const m = new Map();
|
|
34
|
+
Object.defineProperty(window, '__dFns', { value: m, enumerable: false, configurable: true });
|
|
35
|
+
// Patch Function.prototype.toString once to consult the map
|
|
36
|
+
Object.defineProperty(Function.prototype, 'toString', {
|
|
37
|
+
value: function() {
|
|
38
|
+
const override = m.get(this);
|
|
39
|
+
return override !== undefined ? override : _origToString.call(this);
|
|
40
|
+
},
|
|
41
|
+
writable: true, configurable: true
|
|
42
|
+
});
|
|
43
|
+
return m;
|
|
44
|
+
})();
|
|
45
|
+
_patchedFns.set(fn, nativeStr);
|
|
46
|
+
try { Object.defineProperty(fn, 'name', { value: name, configurable: true }); } catch {}
|
|
47
|
+
return fn;
|
|
48
|
+
}`;
|
|
49
|
+
|
|
12
50
|
/**
|
|
13
51
|
* Generate JavaScript source that installs a fetch/XHR interceptor.
|
|
14
52
|
* Captured responses are pushed to `window.__opencli_intercepted`.
|
|
@@ -24,18 +62,24 @@ export function generateInterceptorJs(
|
|
|
24
62
|
const arr = opts.arrayName ?? '__opencli_intercepted';
|
|
25
63
|
const guard = opts.patchGuard ?? '__opencli_interceptor_patched';
|
|
26
64
|
|
|
65
|
+
// Store the current pattern in a separate global so it can be updated
|
|
66
|
+
// without re-patching fetch/XHR (the patchGuard only prevents double-patching).
|
|
67
|
+
const patternVar = `${guard}_pattern`;
|
|
68
|
+
|
|
27
69
|
return `
|
|
28
70
|
() => {
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const __pattern = ${patternExpr};
|
|
71
|
+
${DEFINE_HIDDEN}
|
|
72
|
+
${DISGUISE_FN}
|
|
32
73
|
|
|
33
|
-
if (!window.${
|
|
34
|
-
|
|
74
|
+
if (!window.${arr}) __defHidden(window, '${arr}', []);
|
|
75
|
+
if (!window.${arr}_errors) __defHidden(window, '${arr}_errors', []);
|
|
76
|
+
__defHidden(window, '${patternVar}', ${patternExpr});
|
|
77
|
+
const __checkMatch = (url) => window.${patternVar} && url.includes(window.${patternVar});
|
|
35
78
|
|
|
79
|
+
if (!window.${guard}) {
|
|
36
80
|
// ── Patch fetch ──
|
|
37
81
|
const __origFetch = window.fetch;
|
|
38
|
-
window.fetch = async function(...args) {
|
|
82
|
+
window.fetch = __disguise(async function(...args) {
|
|
39
83
|
const reqUrl = typeof args[0] === 'string' ? args[0]
|
|
40
84
|
: (args[0] && args[0].url) || '';
|
|
41
85
|
const response = await __origFetch.apply(this, args);
|
|
@@ -47,28 +91,28 @@ export function generateInterceptorJs(
|
|
|
47
91
|
} catch(e) { window.${arr}_errors.push({ url: reqUrl, error: String(e) }); }
|
|
48
92
|
}
|
|
49
93
|
return response;
|
|
50
|
-
};
|
|
94
|
+
}, 'fetch');
|
|
51
95
|
|
|
52
96
|
// ── Patch XMLHttpRequest ──
|
|
53
97
|
const __XHR = XMLHttpRequest.prototype;
|
|
54
98
|
const __origOpen = __XHR.open;
|
|
55
99
|
const __origSend = __XHR.send;
|
|
56
|
-
__XHR.open = function(method, url) {
|
|
57
|
-
this
|
|
100
|
+
__XHR.open = __disguise(function(method, url) {
|
|
101
|
+
Object.defineProperty(this, '__iurl', { value: String(url), writable: true, enumerable: false, configurable: true });
|
|
58
102
|
return __origOpen.apply(this, arguments);
|
|
59
|
-
};
|
|
60
|
-
__XHR.send = function() {
|
|
61
|
-
if (__checkMatch(this.
|
|
103
|
+
}, 'open');
|
|
104
|
+
__XHR.send = __disguise(function() {
|
|
105
|
+
if (__checkMatch(this.__iurl)) {
|
|
62
106
|
this.addEventListener('load', function() {
|
|
63
107
|
try {
|
|
64
108
|
window.${arr}.push(JSON.parse(this.responseText));
|
|
65
|
-
} catch(e) { window.${arr}_errors.push({ url: this.
|
|
109
|
+
} catch(e) { window.${arr}_errors.push({ url: this.__iurl, error: String(e) }); }
|
|
66
110
|
});
|
|
67
111
|
}
|
|
68
112
|
return __origSend.apply(this, arguments);
|
|
69
|
-
};
|
|
113
|
+
}, 'send');
|
|
70
114
|
|
|
71
|
-
window
|
|
115
|
+
__defHidden(window, '${guard}', true);
|
|
72
116
|
}
|
|
73
117
|
}
|
|
74
118
|
`;
|
|
@@ -109,13 +153,19 @@ export function generateTapInterceptorJs(patternExpr: string): {
|
|
|
109
153
|
let captureResolve;
|
|
110
154
|
const capturePromise = new Promise(r => { captureResolve = r; });
|
|
111
155
|
const capturePattern = ${patternExpr};
|
|
156
|
+
function __disguise(fn, name) {
|
|
157
|
+
const s = 'function ' + name + '() { [native code] }';
|
|
158
|
+
Object.defineProperty(fn, 'toString', { value: function() { return s; }, writable: true, configurable: true, enumerable: false });
|
|
159
|
+
try { Object.defineProperty(fn, 'name', { value: name, configurable: true }); } catch {}
|
|
160
|
+
return fn;
|
|
161
|
+
}
|
|
112
162
|
`,
|
|
113
163
|
capturedVar: 'captured',
|
|
114
164
|
promiseVar: 'capturePromise',
|
|
115
165
|
resolveVar: 'captureResolve',
|
|
116
166
|
fetchPatch: `
|
|
117
167
|
const origFetch = window.fetch;
|
|
118
|
-
window.fetch = async function(...fetchArgs) {
|
|
168
|
+
window.fetch = __disguise(async function(...fetchArgs) {
|
|
119
169
|
const resp = await origFetch.apply(this, fetchArgs);
|
|
120
170
|
try {
|
|
121
171
|
const url = typeof fetchArgs[0] === 'string' ? fetchArgs[0]
|
|
@@ -125,17 +175,17 @@ export function generateTapInterceptorJs(patternExpr: string): {
|
|
|
125
175
|
}
|
|
126
176
|
} catch {}
|
|
127
177
|
return resp;
|
|
128
|
-
};
|
|
178
|
+
}, 'fetch');
|
|
129
179
|
`,
|
|
130
180
|
xhrPatch: `
|
|
131
181
|
const origXhrOpen = XMLHttpRequest.prototype.open;
|
|
132
182
|
const origXhrSend = XMLHttpRequest.prototype.send;
|
|
133
|
-
XMLHttpRequest.prototype.open = function(method, url) {
|
|
134
|
-
this
|
|
183
|
+
XMLHttpRequest.prototype.open = __disguise(function(method, url) {
|
|
184
|
+
Object.defineProperty(this, '__iurl', { value: String(url), writable: true, enumerable: false, configurable: true });
|
|
135
185
|
return origXhrOpen.apply(this, arguments);
|
|
136
|
-
};
|
|
137
|
-
XMLHttpRequest.prototype.send = function(body) {
|
|
138
|
-
if (capturePattern && this.
|
|
186
|
+
}, 'open');
|
|
187
|
+
XMLHttpRequest.prototype.send = __disguise(function(body) {
|
|
188
|
+
if (capturePattern && this.__iurl?.includes(capturePattern)) {
|
|
139
189
|
this.addEventListener('load', function() {
|
|
140
190
|
if (!captured) {
|
|
141
191
|
try { captured = JSON.parse(this.responseText); captureResolve(); } catch {}
|
|
@@ -143,7 +193,7 @@ export function generateTapInterceptorJs(patternExpr: string): {
|
|
|
143
193
|
});
|
|
144
194
|
}
|
|
145
195
|
return origXhrSend.apply(this, arguments);
|
|
146
|
-
};
|
|
196
|
+
}, 'send');
|
|
147
197
|
`,
|
|
148
198
|
restorePatch: `
|
|
149
199
|
window.fetch = origFetch;
|
package/src/main.ts
CHANGED
|
@@ -19,6 +19,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
19
19
|
import { discoverClis, discoverPlugins } from './discovery.js';
|
|
20
20
|
import { getCompletions } from './completion.js';
|
|
21
21
|
import { runCli } from './cli.js';
|
|
22
|
+
import { emitHook } from './hooks.js';
|
|
22
23
|
|
|
23
24
|
const __filename = fileURLToPath(import.meta.url);
|
|
24
25
|
const __dirname = path.dirname(__filename);
|
|
@@ -49,4 +50,5 @@ if (getCompIdx !== -1) {
|
|
|
49
50
|
process.exit(0);
|
|
50
51
|
}
|
|
51
52
|
|
|
53
|
+
await emitHook('onStartup', { command: '__startup__', args: {} });
|
|
52
54
|
runCli(BUILTIN_CLIS, USER_CLIS);
|
package/src/output.ts
CHANGED
|
@@ -15,6 +15,14 @@ export interface RenderOptions {
|
|
|
15
15
|
footerExtra?: string;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
function normalizeRows(data: unknown): Record<string, unknown>[] {
|
|
19
|
+
return Array.isArray(data) ? data : [data as Record<string, unknown>];
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function resolveColumns(rows: Record<string, unknown>[], opts: RenderOptions): string[] {
|
|
23
|
+
return opts.columns ?? Object.keys(rows[0] ?? {});
|
|
24
|
+
}
|
|
25
|
+
|
|
18
26
|
export function render(data: unknown, opts: RenderOptions = {}): void {
|
|
19
27
|
const fmt = opts.fmt ?? 'table';
|
|
20
28
|
if (data === null || data === undefined) {
|
|
@@ -31,9 +39,9 @@ export function render(data: unknown, opts: RenderOptions = {}): void {
|
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
function renderTable(data: unknown, opts: RenderOptions): void {
|
|
34
|
-
const rows =
|
|
42
|
+
const rows = normalizeRows(data);
|
|
35
43
|
if (!rows.length) { console.log(chalk.dim('(no data)')); return; }
|
|
36
|
-
const columns =
|
|
44
|
+
const columns = resolveColumns(rows, opts);
|
|
37
45
|
|
|
38
46
|
const header = columns.map(c => capitalize(c));
|
|
39
47
|
const table = new Table({
|
|
@@ -66,9 +74,9 @@ function renderJson(data: unknown): void {
|
|
|
66
74
|
}
|
|
67
75
|
|
|
68
76
|
function renderMarkdown(data: unknown, opts: RenderOptions): void {
|
|
69
|
-
const rows =
|
|
77
|
+
const rows = normalizeRows(data);
|
|
70
78
|
if (!rows.length) return;
|
|
71
|
-
const columns =
|
|
79
|
+
const columns = resolveColumns(rows, opts);
|
|
72
80
|
console.log('| ' + columns.join(' | ') + ' |');
|
|
73
81
|
console.log('| ' + columns.map(() => '---').join(' | ') + ' |');
|
|
74
82
|
for (const row of rows) {
|
|
@@ -77,9 +85,9 @@ function renderMarkdown(data: unknown, opts: RenderOptions): void {
|
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
function renderCsv(data: unknown, opts: RenderOptions): void {
|
|
80
|
-
const rows =
|
|
88
|
+
const rows = normalizeRows(data);
|
|
81
89
|
if (!rows.length) return;
|
|
82
|
-
const columns =
|
|
90
|
+
const columns = resolveColumns(rows, opts);
|
|
83
91
|
console.log(columns.join(','));
|
|
84
92
|
for (const row of rows) {
|
|
85
93
|
console.log(columns.map(c => {
|
package/src/pipeline/executor.ts
CHANGED
|
@@ -16,7 +16,7 @@ export interface PipelineContext {
|
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/** Steps that interact with the browser and may fail transiently */
|
|
19
|
-
const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot'
|
|
19
|
+
const BROWSER_STEPS = new Set(['navigate', 'evaluate', 'click', 'type', 'press', 'wait', 'snapshot']);
|
|
20
20
|
|
|
21
21
|
export async function executePipeline(
|
|
22
22
|
page: IPage | null,
|
|
@@ -6,9 +6,7 @@
|
|
|
6
6
|
import type { IPage } from '../../types.js';
|
|
7
7
|
import { render } from '../template.js';
|
|
8
8
|
|
|
9
|
-
|
|
10
|
-
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
11
|
-
}
|
|
9
|
+
import { isRecord } from '../../utils.js';
|
|
12
10
|
|
|
13
11
|
export async function stepNavigate(page: IPage | null, params: unknown, data: unknown, args: Record<string, unknown>): Promise<unknown> {
|
|
14
12
|
if (isRecord(params) && 'url' in params) {
|