@jackwener/opencli 0.1.0 → 0.1.1

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.
Files changed (52) hide show
  1. package/CLI-CREATOR.md +594 -0
  2. package/README.md +116 -38
  3. package/README.zh-CN.md +143 -0
  4. package/SKILL.md +154 -102
  5. package/dist/browser.d.ts +1 -0
  6. package/dist/browser.js +35 -1
  7. package/dist/cascade.d.ts +45 -0
  8. package/dist/cascade.js +180 -0
  9. package/dist/clis/bilibili/hot.yaml +38 -0
  10. package/dist/clis/github/trending.yaml +58 -0
  11. package/dist/clis/hackernews/top.yaml +36 -0
  12. package/dist/clis/index.d.ts +2 -1
  13. package/dist/clis/index.js +3 -1
  14. package/dist/clis/reddit/hot.yaml +46 -0
  15. package/dist/clis/twitter/trending.yaml +40 -0
  16. package/dist/clis/v2ex/hot.yaml +25 -0
  17. package/dist/clis/v2ex/latest.yaml +25 -0
  18. package/dist/clis/v2ex/topic.yaml +27 -0
  19. package/dist/clis/xiaohongshu/feed.yaml +32 -0
  20. package/dist/clis/xiaohongshu/notifications.yaml +38 -0
  21. package/dist/clis/xiaohongshu/search.d.ts +5 -0
  22. package/dist/clis/xiaohongshu/search.js +68 -0
  23. package/dist/clis/zhihu/hot.yaml +42 -0
  24. package/dist/clis/zhihu/question.js +39 -0
  25. package/dist/clis/zhihu/search.yaml +55 -0
  26. package/dist/explore.d.ts +23 -13
  27. package/dist/explore.js +293 -422
  28. package/dist/main.js +17 -0
  29. package/dist/pipeline.js +238 -2
  30. package/dist/synthesize.d.ts +11 -8
  31. package/dist/synthesize.js +142 -118
  32. package/package.json +4 -2
  33. package/src/browser.ts +33 -1
  34. package/src/cascade.ts +217 -0
  35. package/src/clis/index.ts +4 -1
  36. package/src/clis/reddit/hot.yaml +46 -0
  37. package/src/clis/v2ex/hot.yaml +5 -9
  38. package/src/clis/v2ex/latest.yaml +5 -8
  39. package/src/clis/v2ex/topic.yaml +27 -0
  40. package/src/clis/xiaohongshu/feed.yaml +32 -0
  41. package/src/clis/xiaohongshu/notifications.yaml +38 -0
  42. package/src/clis/xiaohongshu/search.ts +71 -0
  43. package/src/clis/zhihu/hot.yaml +22 -8
  44. package/src/clis/zhihu/question.ts +45 -0
  45. package/src/clis/zhihu/search.yaml +55 -0
  46. package/src/explore.ts +303 -465
  47. package/src/main.ts +14 -0
  48. package/src/pipeline.ts +239 -2
  49. package/src/synthesize.ts +142 -137
  50. package/dist/clis/zhihu/search.js +0 -58
  51. package/src/clis/zhihu/search.ts +0 -65
  52. /package/dist/clis/zhihu/{search.d.ts → question.d.ts} +0 -0
@@ -0,0 +1,38 @@
1
+ site: bilibili
2
+ name: hot
3
+ description: B站热门视频
4
+ domain: www.bilibili.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 20
10
+ description: Number of videos
11
+
12
+ pipeline:
13
+ - navigate: https://www.bilibili.com
14
+
15
+ - evaluate: |
16
+ (async () => {
17
+ const res = await fetch('https://api.bilibili.com/x/web-interface/popular?ps=${{ args.limit }}&pn=1', {
18
+ credentials: 'include'
19
+ });
20
+ const data = await res.json();
21
+ return (data?.data?.list || []).map((item) => ({
22
+ title: item.title,
23
+ author: item.owner?.name,
24
+ play: item.stat?.view,
25
+ danmaku: item.stat?.danmaku,
26
+ }));
27
+ })()
28
+
29
+ - map:
30
+ rank: ${{ index + 1 }}
31
+ title: ${{ item.title }}
32
+ author: ${{ item.author }}
33
+ play: ${{ item.play }}
34
+ danmaku: ${{ item.danmaku }}
35
+
36
+ - limit: ${{ args.limit }}
37
+
38
+ columns: [rank, title, author, play, danmaku]
@@ -0,0 +1,58 @@
1
+ site: github
2
+ name: trending
3
+ description: GitHub trending repositories
4
+ domain: github.com
5
+
6
+ args:
7
+ language:
8
+ type: str
9
+ default: ""
10
+ description: "Programming language filter (e.g. python, rust)"
11
+ since:
12
+ type: str
13
+ default: daily
14
+ description: "Time range: daily, weekly, monthly"
15
+ limit:
16
+ type: int
17
+ default: 20
18
+ description: Number of repos
19
+
20
+ pipeline:
21
+ - evaluate: |
22
+ (async () => {
23
+ const lang = '${{ args.language }}' ? '/${{ args.language }}' : '';
24
+ const res = await fetch(`https://github.com/trending${lang}?since=${{ args.since }}`, {
25
+ headers: { 'Accept': 'text/html' }
26
+ });
27
+ const html = await res.text();
28
+ const parser = new DOMParser();
29
+ const doc = parser.parseFromString(html, 'text/html');
30
+ const rows = doc.querySelectorAll('article.Box-row');
31
+ return Array.from(rows).map(row => {
32
+ const nameEl = row.querySelector('h2 a');
33
+ const descEl = row.querySelector('p');
34
+ const langEl = row.querySelector('[itemprop="programmingLanguage"]');
35
+ const starsEl = row.querySelectorAll('a.Link--muted');
36
+ const todayEl = row.querySelector('span.d-inline-block.float-sm-right');
37
+ return {
38
+ repo: nameEl?.textContent?.trim()?.replace(/\s+/g, '') || '',
39
+ description: descEl?.textContent?.trim() || '',
40
+ language: langEl?.textContent?.trim() || '',
41
+ stars: starsEl[0]?.textContent?.trim() || '',
42
+ forks: starsEl[1]?.textContent?.trim() || '',
43
+ today: todayEl?.textContent?.trim() || ''
44
+ };
45
+ });
46
+ })()
47
+
48
+ - map:
49
+ rank: ${{ index + 1 }}
50
+ repo: ${{ item.repo }}
51
+ description: ${{ item.description }}
52
+ language: ${{ item.language }}
53
+ stars: ${{ item.stars }}
54
+ today: ${{ item.today }}
55
+
56
+ - limit: ${{ args.limit }}
57
+
58
+ columns: [rank, repo, language, stars, today]
@@ -0,0 +1,36 @@
1
+ site: hackernews
2
+ name: top
3
+ description: Hacker News top stories
4
+ domain: news.ycombinator.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of stories
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://hacker-news.firebaseio.com/v0/topstories.json
17
+
18
+ - limit: 30
19
+
20
+ - map:
21
+ id: ${{ item }}
22
+
23
+ - fetch:
24
+ url: https://hacker-news.firebaseio.com/v0/item/${{ item.id }}.json
25
+
26
+ - map:
27
+ rank: ${{ index + 1 }}
28
+ title: ${{ item.title }}
29
+ score: ${{ item.score }}
30
+ author: ${{ item.by }}
31
+ comments: ${{ item.descendants }}
32
+ url: ${{ item.url }}
33
+
34
+ - limit: ${{ args.limit }}
35
+
36
+ columns: [rank, title, score, author, comments]
@@ -10,4 +10,5 @@ import './bilibili/history.js';
10
10
  import './bilibili/feed.js';
11
11
  import './bilibili/user-videos.js';
12
12
  import './github/search.js';
13
- import './zhihu/search.js';
13
+ import './zhihu/question.js';
14
+ import './xiaohongshu/search.js';
@@ -13,4 +13,6 @@ import './bilibili/user-videos.js';
13
13
  // github
14
14
  import './github/search.js';
15
15
  // zhihu
16
- import './zhihu/search.js';
16
+ import './zhihu/question.js';
17
+ // xiaohongshu
18
+ import './xiaohongshu/search.js';
@@ -0,0 +1,46 @@
1
+ site: reddit
2
+ name: hot
3
+ description: Reddit 热门帖子
4
+ domain: www.reddit.com
5
+
6
+ args:
7
+ subreddit:
8
+ type: str
9
+ default: ""
10
+ description: "Subreddit name (e.g. programming). Empty for frontpage"
11
+ limit:
12
+ type: int
13
+ default: 20
14
+ description: Number of posts
15
+
16
+ pipeline:
17
+ - navigate: https://www.reddit.com
18
+
19
+ - evaluate: |
20
+ (async () => {
21
+ const sub = '${{ args.subreddit }}';
22
+ const path = sub ? '/r/' + sub + '/hot.json' : '/hot.json';
23
+ const res = await fetch(path + '?limit=${{ args.limit }}&raw_json=1', {
24
+ credentials: 'include'
25
+ });
26
+ const d = await res.json();
27
+ return (d?.data?.children || []).map(c => ({
28
+ title: c.data.title,
29
+ subreddit: c.data.subreddit_name_prefixed,
30
+ score: c.data.score,
31
+ comments: c.data.num_comments,
32
+ author: c.data.author,
33
+ url: 'https://www.reddit.com' + c.data.permalink,
34
+ }));
35
+ })()
36
+
37
+ - map:
38
+ rank: ${{ index + 1 }}
39
+ title: ${{ item.title }}
40
+ subreddit: ${{ item.subreddit }}
41
+ score: ${{ item.score }}
42
+ comments: ${{ item.comments }}
43
+
44
+ - limit: ${{ args.limit }}
45
+
46
+ columns: [rank, title, subreddit, score, comments]
@@ -0,0 +1,40 @@
1
+ site: twitter
2
+ name: trending
3
+ description: Twitter/X trending topics
4
+ domain: x.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 20
10
+ description: Number of trends to show
11
+
12
+ pipeline:
13
+ - navigate: https://x.com/explore/tabs/trending
14
+
15
+ - evaluate: |
16
+ (async () => {
17
+ const cookies = document.cookie.split(';').reduce((acc, c) => {
18
+ const [k, v] = c.trim().split('=');
19
+ acc[k] = v;
20
+ return acc;
21
+ }, {});
22
+ const csrfToken = cookies['ct0'] || '';
23
+ const bearerToken = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
24
+ const res = await fetch('/i/api/2/guide.json?include_page_configuration=true', {
25
+ credentials: 'include',
26
+ headers: { 'x-twitter-active-user': 'yes', 'x-csrf-token': csrfToken, 'authorization': 'Bearer ' + bearerToken }
27
+ });
28
+ const data = await res.json();
29
+ const trends = data?.timeline?.instructions?.[1]?.addEntries?.entries || [];
30
+ return trends.filter(e => e.content?.timelineModule).flatMap(e => e.content.timelineModule.items || []).map(t => t?.item?.content?.trend).filter(Boolean);
31
+ })()
32
+
33
+ - map:
34
+ rank: ${{ index + 1 }}
35
+ topic: ${{ item.name }}
36
+ tweets: ${{ item.tweetCount || 'N/A' }}
37
+
38
+ - limit: ${{ args.limit }}
39
+
40
+ columns: [rank, topic, tweets]
@@ -0,0 +1,25 @@
1
+ site: v2ex
2
+ name: hot
3
+ description: V2EX 热门话题
4
+ domain: www.v2ex.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of topics
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://www.v2ex.com/api/topics/hot.json
17
+
18
+ - map:
19
+ rank: ${{ index + 1 }}
20
+ title: ${{ item.title }}
21
+ replies: ${{ item.replies }}
22
+
23
+ - limit: ${{ args.limit }}
24
+
25
+ columns: [rank, title, replies]
@@ -0,0 +1,25 @@
1
+ site: v2ex
2
+ name: latest
3
+ description: V2EX 最新话题
4
+ domain: www.v2ex.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of topics
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://www.v2ex.com/api/topics/latest.json
17
+
18
+ - map:
19
+ rank: ${{ index + 1 }}
20
+ title: ${{ item.title }}
21
+ replies: ${{ item.replies }}
22
+
23
+ - limit: ${{ args.limit }}
24
+
25
+ columns: [rank, title, replies]
@@ -0,0 +1,27 @@
1
+ site: v2ex
2
+ name: topic
3
+ description: V2EX 主题详情和回复
4
+ domain: www.v2ex.com
5
+ strategy: public
6
+ browser: false
7
+
8
+ args:
9
+ id:
10
+ type: str
11
+ required: true
12
+ description: Topic ID
13
+
14
+ pipeline:
15
+ - fetch:
16
+ url: https://www.v2ex.com/api/topics/show.json
17
+ params:
18
+ id: ${{ args.id }}
19
+
20
+ - map:
21
+ title: ${{ item.title }}
22
+ replies: ${{ item.replies }}
23
+ url: ${{ item.url }}
24
+
25
+ - limit: 1
26
+
27
+ columns: [title, replies, url]
@@ -0,0 +1,32 @@
1
+ site: xiaohongshu
2
+ name: feed
3
+ description: "小红书首页推荐 Feed (via Pinia Store Action)"
4
+ domain: www.xiaohongshu.com
5
+ strategy: intercept
6
+ browser: true
7
+
8
+ args:
9
+ limit:
10
+ type: int
11
+ default: 20
12
+ description: Number of items to return
13
+
14
+ columns: [title, author, likes, type, url]
15
+
16
+ pipeline:
17
+ - navigate: https://www.xiaohongshu.com/explore
18
+ - wait: 3
19
+ - tap:
20
+ store: feed
21
+ action: fetchFeeds
22
+ capture: homefeed
23
+ select: data.items
24
+ timeout: 8
25
+ - map:
26
+ id: ${{ item.id }}
27
+ title: ${{ item.note_card.display_title }}
28
+ type: ${{ item.note_card.type }}
29
+ author: ${{ item.note_card.user.nickname }}
30
+ likes: ${{ item.note_card.interact_info.liked_count }}
31
+ url: https://www.xiaohongshu.com/explore/${{ item.id }}
32
+ - limit: ${{ args.limit | default(20) }}
@@ -0,0 +1,38 @@
1
+ site: xiaohongshu
2
+ name: notifications
3
+ description: "小红书通知 (mentions/likes/connections)"
4
+ domain: www.xiaohongshu.com
5
+ strategy: intercept
6
+ browser: true
7
+
8
+ args:
9
+ type:
10
+ type: str
11
+ default: mentions
12
+ description: "Notification type: mentions, likes, or connections"
13
+ limit:
14
+ type: int
15
+ default: 20
16
+ description: Number of notifications to return
17
+
18
+ columns: [rank, user, action, content, note, time]
19
+
20
+ pipeline:
21
+ - navigate: https://www.xiaohongshu.com/notification
22
+ - wait: 3
23
+ - tap:
24
+ store: notification
25
+ action: getNotification
26
+ args:
27
+ - ${{ args.type | default('mentions') }}
28
+ capture: /you/
29
+ select: data.message_list
30
+ timeout: 8
31
+ - map:
32
+ rank: ${{ index + 1 }}
33
+ user: ${{ item.user_info.nickname }}
34
+ action: ${{ item.title }}
35
+ content: ${{ item.comment_info.content }}
36
+ note: ${{ item.item_info.content }}
37
+ time: ${{ item.time }}
38
+ - limit: ${{ args.limit | default(20) }}
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Xiaohongshu search — trigger search via Pinia store + XHR interception.
3
+ * Inspired by bb-sites/xiaohongshu/search.js but adapted for opencli pipeline.
4
+ */
5
+ export {};
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Xiaohongshu search — trigger search via Pinia store + XHR interception.
3
+ * Inspired by bb-sites/xiaohongshu/search.js but adapted for opencli pipeline.
4
+ */
5
+ import { cli, Strategy } from '../../registry.js';
6
+ cli({
7
+ site: 'xiaohongshu',
8
+ name: 'search',
9
+ description: '搜索小红书笔记',
10
+ domain: 'www.xiaohongshu.com',
11
+ strategy: Strategy.COOKIE,
12
+ args: [
13
+ { name: 'keyword', required: true, help: 'Search keyword' },
14
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
15
+ ],
16
+ columns: ['rank', 'title', 'author', 'likes', 'type'],
17
+ func: async (page, kwargs) => {
18
+ await page.goto('https://www.xiaohongshu.com');
19
+ await page.wait(2);
20
+ const data = await page.evaluate(`
21
+ (async () => {
22
+ const app = document.querySelector('#app')?.__vue_app__;
23
+ const pinia = app?.config?.globalProperties?.$pinia;
24
+ if (!pinia?._s) return {error: 'Page not ready'};
25
+
26
+ const searchStore = pinia._s.get('search');
27
+ if (!searchStore) return {error: 'Search store not found'};
28
+
29
+ let captured = null;
30
+ const origOpen = XMLHttpRequest.prototype.open;
31
+ const origSend = XMLHttpRequest.prototype.send;
32
+ XMLHttpRequest.prototype.open = function(m, u) { this.__url = u; return origOpen.apply(this, arguments); };
33
+ XMLHttpRequest.prototype.send = function(b) {
34
+ if (this.__url?.includes('search/notes')) {
35
+ const x = this;
36
+ const orig = x.onreadystatechange;
37
+ x.onreadystatechange = function() { if (x.readyState === 4 && !captured) { try { captured = JSON.parse(x.responseText); } catch {} } if (orig) orig.apply(this, arguments); };
38
+ }
39
+ return origSend.apply(this, arguments);
40
+ };
41
+
42
+ try {
43
+ searchStore.mutateSearchValue('${kwargs.keyword}');
44
+ await searchStore.loadMore();
45
+ await new Promise(r => setTimeout(r, 800));
46
+ } finally {
47
+ XMLHttpRequest.prototype.open = origOpen;
48
+ XMLHttpRequest.prototype.send = origSend;
49
+ }
50
+
51
+ if (!captured?.success) return {error: captured?.msg || 'Search failed'};
52
+ return (captured.data?.items || []).map(i => ({
53
+ title: i.note_card?.display_title || '',
54
+ type: i.note_card?.type || '',
55
+ url: 'https://www.xiaohongshu.com/explore/' + i.id,
56
+ author: i.note_card?.user?.nickname || '',
57
+ likes: i.note_card?.interact_info?.liked_count || '0',
58
+ }));
59
+ })()
60
+ `);
61
+ if (!Array.isArray(data))
62
+ return [];
63
+ return data.slice(0, kwargs.limit).map((item, i) => ({
64
+ rank: i + 1,
65
+ ...item,
66
+ }));
67
+ },
68
+ });
@@ -0,0 +1,42 @@
1
+ site: zhihu
2
+ name: hot
3
+ description: 知乎热榜
4
+ domain: www.zhihu.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 20
10
+ description: Number of items to return
11
+
12
+ pipeline:
13
+ - navigate: https://www.zhihu.com
14
+
15
+ - evaluate: |
16
+ (async () => {
17
+ const res = await fetch('https://www.zhihu.com/api/v3/feed/topstory/hot-lists/total?limit=50', {
18
+ credentials: 'include'
19
+ });
20
+ const d = await res.json();
21
+ return (d?.data || []).map((item) => {
22
+ const t = item.target || {};
23
+ return {
24
+ title: t.title,
25
+ url: 'https://www.zhihu.com/question/' + t.id,
26
+ answer_count: t.answer_count,
27
+ follower_count: t.follower_count,
28
+ heat: item.detail_text || '',
29
+ };
30
+ });
31
+ })()
32
+
33
+ - map:
34
+ rank: ${{ index + 1 }}
35
+ title: ${{ item.title }}
36
+ heat: ${{ item.heat }}
37
+ answers: ${{ item.answer_count }}
38
+ url: ${{ item.url }}
39
+
40
+ - limit: ${{ args.limit }}
41
+
42
+ columns: [rank, title, heat, answers]
@@ -0,0 +1,39 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ cli({
3
+ site: 'zhihu',
4
+ name: 'question',
5
+ description: '知乎问题详情和回答',
6
+ domain: 'www.zhihu.com',
7
+ strategy: Strategy.COOKIE,
8
+ args: [
9
+ { name: 'id', required: true, help: 'Question ID (numeric)' },
10
+ { name: 'limit', type: 'int', default: 5, help: 'Number of answers' },
11
+ ],
12
+ columns: ['rank', 'author', 'votes', 'content'],
13
+ func: async (page, kwargs) => {
14
+ const { id, limit = 5 } = kwargs;
15
+ const stripHtml = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').trim();
16
+ // Fetch question detail and answers in parallel via evaluate
17
+ const result = await page.evaluate(`
18
+ async () => {
19
+ const [qResp, aResp] = await Promise.all([
20
+ fetch('https://www.zhihu.com/api/v4/questions/${id}?include=data[*].detail,excerpt,answer_count,follower_count,visit_count', {credentials: 'include'}),
21
+ fetch('https://www.zhihu.com/api/v4/questions/${id}/answers?limit=${limit}&offset=0&sort_by=default&include=data[*].content,voteup_count,comment_count,author', {credentials: 'include'})
22
+ ]);
23
+ if (!qResp.ok || !aResp.ok) return { error: true };
24
+ const q = await qResp.json();
25
+ const a = await aResp.json();
26
+ return { question: q, answers: a.data || [] };
27
+ }
28
+ `);
29
+ if (!result || result.error)
30
+ throw new Error('Failed to fetch question. Are you logged in?');
31
+ const answers = (result.answers ?? []).slice(0, Number(limit)).map((a, i) => ({
32
+ rank: i + 1,
33
+ author: a.author?.name ?? 'anonymous',
34
+ votes: a.voteup_count ?? 0,
35
+ content: stripHtml(a.content ?? '').slice(0, 200),
36
+ }));
37
+ return answers;
38
+ },
39
+ });
@@ -0,0 +1,55 @@
1
+ site: zhihu
2
+ name: search
3
+ description: 知乎搜索
4
+ domain: www.zhihu.com
5
+
6
+ args:
7
+ keyword:
8
+ type: str
9
+ required: true
10
+ description: Search keyword
11
+ limit:
12
+ type: int
13
+ default: 10
14
+ description: Number of results
15
+
16
+ pipeline:
17
+ - navigate: https://www.zhihu.com
18
+
19
+ - evaluate: |
20
+ (async () => {
21
+ const strip = (html) => (html || '').replace(/<[^>]+>/g, '').replace(/&nbsp;/g, ' ').replace(/&lt;/g, '<').replace(/&gt;/g, '>').replace(/&amp;/g, '&').replace(/<em>/g, '').replace(/<\/em>/g, '').trim();
22
+ const res = await fetch('https://www.zhihu.com/api/v4/search_v3?q=' + encodeURIComponent('${{ args.keyword }}') + '&t=general&offset=0&limit=${{ args.limit }}', {
23
+ credentials: 'include'
24
+ });
25
+ const d = await res.json();
26
+ return (d?.data || [])
27
+ .filter(item => item.type === 'search_result')
28
+ .map(item => {
29
+ const obj = item.object || {};
30
+ const q = obj.question || {};
31
+ return {
32
+ type: obj.type,
33
+ title: strip(obj.title || q.name || ''),
34
+ excerpt: strip(obj.excerpt || '').substring(0, 100),
35
+ author: obj.author?.name || '',
36
+ votes: obj.voteup_count || 0,
37
+ url: obj.type === 'answer'
38
+ ? 'https://www.zhihu.com/question/' + q.id + '/answer/' + obj.id
39
+ : obj.type === 'article'
40
+ ? 'https://zhuanlan.zhihu.com/p/' + obj.id
41
+ : 'https://www.zhihu.com/question/' + obj.id,
42
+ };
43
+ });
44
+ })()
45
+
46
+ - map:
47
+ rank: ${{ index + 1 }}
48
+ title: ${{ item.title }}
49
+ type: ${{ item.type }}
50
+ author: ${{ item.author }}
51
+ votes: ${{ item.votes }}
52
+
53
+ - limit: ${{ args.limit }}
54
+
55
+ columns: [rank, title, type, author, votes]
package/dist/explore.d.ts CHANGED
@@ -1,17 +1,27 @@
1
1
  /**
2
2
  * Deep Explore: intelligent API discovery with response analysis.
3
3
  *
4
- * Unlike simple page snapshots, Deep Explore intercepts network traffic,
5
- * analyzes response schemas, and automatically infers capabilities that
6
- * can be turned into CLI commands.
7
- *
8
- * Flow:
9
- * 1. Navigate to target URL
10
- * 2. Auto-scroll to trigger lazy loading
11
- * 3. Capture network requests (with body analysis)
12
- * 4. For each JSON response: detect list fields, infer columns, analyze auth
13
- * 5. Detect frontend framework (Vue/React/Pinia/Next.js)
14
- * 6. Generate structured capabilities.json
4
+ * Navigates to the target URL, auto-scrolls to trigger lazy loading,
5
+ * captures network traffic, analyzes JSON responses, and automatically
6
+ * infers CLI capabilities from discovered API endpoints.
15
7
  */
16
- export declare function exploreUrl(url: string, opts?: any): Promise<any>;
17
- export declare function renderExploreSummary(result: any): string;
8
+ export declare function detectSiteName(url: string): string;
9
+ export declare function slugify(value: string): string;
10
+ export interface DiscoveredStore {
11
+ type: 'pinia' | 'vuex';
12
+ id: string;
13
+ actions: string[];
14
+ stateKeys: string[];
15
+ }
16
+ export declare function exploreUrl(url: string, opts: {
17
+ BrowserFactory: new () => any;
18
+ site?: string;
19
+ goal?: string;
20
+ authenticated?: boolean;
21
+ outDir?: string;
22
+ waitSeconds?: number;
23
+ query?: string;
24
+ clickLabels?: string[];
25
+ auto?: boolean;
26
+ }): Promise<Record<string, any>>;
27
+ export declare function renderExploreSummary(result: Record<string, any>): string;