@jackwener/opencli 0.1.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.
Files changed (94) hide show
  1. package/.github/workflows/ci.yml +26 -0
  2. package/.github/workflows/release.yml +40 -0
  3. package/README.md +67 -0
  4. package/SKILL.md +230 -0
  5. package/dist/bilibili.d.ts +13 -0
  6. package/dist/bilibili.js +93 -0
  7. package/dist/browser.d.ts +48 -0
  8. package/dist/browser.js +261 -0
  9. package/dist/clis/bilibili/favorite.d.ts +1 -0
  10. package/dist/clis/bilibili/favorite.js +39 -0
  11. package/dist/clis/bilibili/feed.d.ts +1 -0
  12. package/dist/clis/bilibili/feed.js +64 -0
  13. package/dist/clis/bilibili/history.d.ts +1 -0
  14. package/dist/clis/bilibili/history.js +44 -0
  15. package/dist/clis/bilibili/me.d.ts +1 -0
  16. package/dist/clis/bilibili/me.js +13 -0
  17. package/dist/clis/bilibili/search.d.ts +1 -0
  18. package/dist/clis/bilibili/search.js +24 -0
  19. package/dist/clis/bilibili/user-videos.d.ts +1 -0
  20. package/dist/clis/bilibili/user-videos.js +38 -0
  21. package/dist/clis/github/search.d.ts +1 -0
  22. package/dist/clis/github/search.js +20 -0
  23. package/dist/clis/index.d.ts +13 -0
  24. package/dist/clis/index.js +16 -0
  25. package/dist/clis/zhihu/search.d.ts +1 -0
  26. package/dist/clis/zhihu/search.js +58 -0
  27. package/dist/engine.d.ts +6 -0
  28. package/dist/engine.js +77 -0
  29. package/dist/explore.d.ts +17 -0
  30. package/dist/explore.js +603 -0
  31. package/dist/generate.d.ts +11 -0
  32. package/dist/generate.js +134 -0
  33. package/dist/main.d.ts +5 -0
  34. package/dist/main.js +117 -0
  35. package/dist/output.d.ts +11 -0
  36. package/dist/output.js +98 -0
  37. package/dist/pipeline.d.ts +9 -0
  38. package/dist/pipeline.js +315 -0
  39. package/dist/promote.d.ts +1 -0
  40. package/dist/promote.js +3 -0
  41. package/dist/register.d.ts +2 -0
  42. package/dist/register.js +2 -0
  43. package/dist/registry.d.ts +50 -0
  44. package/dist/registry.js +42 -0
  45. package/dist/runtime.d.ts +12 -0
  46. package/dist/runtime.js +27 -0
  47. package/dist/scaffold.d.ts +2 -0
  48. package/dist/scaffold.js +2 -0
  49. package/dist/smoke.d.ts +2 -0
  50. package/dist/smoke.js +2 -0
  51. package/dist/snapshotFormatter.d.ts +9 -0
  52. package/dist/snapshotFormatter.js +41 -0
  53. package/dist/synthesize.d.ts +10 -0
  54. package/dist/synthesize.js +191 -0
  55. package/dist/validate.d.ts +2 -0
  56. package/dist/validate.js +73 -0
  57. package/dist/verify.d.ts +2 -0
  58. package/dist/verify.js +9 -0
  59. package/package.json +47 -0
  60. package/src/bilibili.ts +111 -0
  61. package/src/browser.ts +260 -0
  62. package/src/clis/bilibili/favorite.ts +42 -0
  63. package/src/clis/bilibili/feed.ts +71 -0
  64. package/src/clis/bilibili/history.ts +48 -0
  65. package/src/clis/bilibili/hot.yaml +38 -0
  66. package/src/clis/bilibili/me.ts +14 -0
  67. package/src/clis/bilibili/search.ts +25 -0
  68. package/src/clis/bilibili/user-videos.ts +42 -0
  69. package/src/clis/github/search.ts +21 -0
  70. package/src/clis/github/trending.yaml +58 -0
  71. package/src/clis/hackernews/top.yaml +36 -0
  72. package/src/clis/index.ts +19 -0
  73. package/src/clis/twitter/trending.yaml +40 -0
  74. package/src/clis/v2ex/hot.yaml +29 -0
  75. package/src/clis/v2ex/latest.yaml +28 -0
  76. package/src/clis/zhihu/hot.yaml +28 -0
  77. package/src/clis/zhihu/search.ts +65 -0
  78. package/src/engine.ts +86 -0
  79. package/src/explore.ts +648 -0
  80. package/src/generate.ts +145 -0
  81. package/src/main.ts +103 -0
  82. package/src/output.ts +96 -0
  83. package/src/pipeline.ts +295 -0
  84. package/src/promote.ts +3 -0
  85. package/src/register.ts +2 -0
  86. package/src/registry.ts +87 -0
  87. package/src/runtime.ts +36 -0
  88. package/src/scaffold.ts +2 -0
  89. package/src/smoke.ts +2 -0
  90. package/src/snapshotFormatter.ts +51 -0
  91. package/src/synthesize.ts +210 -0
  92. package/src/validate.ts +55 -0
  93. package/src/verify.ts +9 -0
  94. package/tsconfig.json +17 -0
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Import all TypeScript CLI adapters so they self-register.
3
+ *
4
+ * Each TS adapter calls cli() on import, which adds itself to the global registry.
5
+ */
6
+
7
+ // bilibili
8
+ import './bilibili/search.js';
9
+ import './bilibili/me.js';
10
+ import './bilibili/favorite.js';
11
+ import './bilibili/history.js';
12
+ import './bilibili/feed.js';
13
+ import './bilibili/user-videos.js';
14
+
15
+ // github
16
+ import './github/search.js';
17
+
18
+ // zhihu
19
+ import './zhihu/search.js';
@@ -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,29 @@
1
+ site: v2ex
2
+ name: hot
3
+ description: V2EX 热门话题
4
+ domain: www.v2ex.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 20
10
+ description: Number of topics
11
+
12
+ pipeline:
13
+ - evaluate: |
14
+ (async () => {
15
+ const res = await fetch('https://www.v2ex.com/api/topics/hot.json');
16
+ return await res.json();
17
+ })()
18
+
19
+ - map:
20
+ rank: ${{ index + 1 }}
21
+ title: ${{ item.title }}
22
+ node: ${{ item.node?.title }}
23
+ author: ${{ item.member?.username }}
24
+ replies: ${{ item.replies }}
25
+ url: ${{ item.url }}
26
+
27
+ - limit: ${{ args.limit }}
28
+
29
+ columns: [rank, title, node, author, replies]
@@ -0,0 +1,28 @@
1
+ site: v2ex
2
+ name: latest
3
+ description: V2EX 最新话题
4
+ domain: www.v2ex.com
5
+
6
+ args:
7
+ limit:
8
+ type: int
9
+ default: 20
10
+ description: Number of topics
11
+
12
+ pipeline:
13
+ - evaluate: |
14
+ (async () => {
15
+ const res = await fetch('https://www.v2ex.com/api/topics/latest.json');
16
+ return await res.json();
17
+ })()
18
+
19
+ - map:
20
+ rank: ${{ index + 1 }}
21
+ title: ${{ item.title }}
22
+ node: ${{ item.node?.title }}
23
+ author: ${{ item.member?.username }}
24
+ replies: ${{ item.replies }}
25
+
26
+ - limit: ${{ args.limit }}
27
+
28
+ columns: [rank, title, node, author, replies]
@@ -0,0 +1,28 @@
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
+ - fetch:
16
+ url: https://www.zhihu.com/api/v4/search/top_search
17
+ params:
18
+ limit: ${{ args.limit }}
19
+
20
+ - select: top_search.words
21
+
22
+ - map:
23
+ rank: ${{ index + 1 }}
24
+ title: ${{ item.query }}
25
+
26
+ - limit: ${{ args.limit }}
27
+
28
+ columns: [rank, title]
@@ -0,0 +1,65 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ import { fetchJson } from '../../bilibili.js';
3
+
4
+ cli({
5
+ site: 'zhihu',
6
+ name: 'search',
7
+ description: '搜索知乎问题和回答',
8
+ domain: 'www.zhihu.com',
9
+ strategy: Strategy.COOKIE,
10
+ args: [
11
+ { name: 'keyword', required: true, help: 'Search keyword' },
12
+ { name: 'type', default: 'general', help: 'general, article, video' },
13
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
14
+ ],
15
+ columns: ['rank', 'title', 'author', 'type', 'url'],
16
+ func: async (page, kwargs) => {
17
+ const { keyword, type = 'general', limit = 20 } = kwargs;
18
+
19
+ // Navigate to zhihu to ensure cookie context
20
+ await page.goto('https://www.zhihu.com');
21
+
22
+ const qs = new URLSearchParams({ q: keyword, type, limit: String(limit) });
23
+ const payload = await fetchJson(page, `https://www.zhihu.com/api/v4/search_v3?${qs}`);
24
+
25
+ const data: any[] = payload?.data ?? [];
26
+ const rows: any[] = [];
27
+
28
+ for (let i = 0; i < Math.min(data.length, Number(limit)); i++) {
29
+ const item = data[i];
30
+ const obj = item.object ?? item;
31
+ const itemType = item.type ?? obj.type ?? 'unknown';
32
+
33
+ let title = '';
34
+ let author = '';
35
+ let url = '';
36
+
37
+ if (itemType === 'search_result') {
38
+ const highlight = obj.highlight ?? {};
39
+ title = (highlight.title ?? obj.title ?? '').replace(/<[^>]+>/g, '');
40
+ author = obj.author?.name ?? '';
41
+ url = obj.url ?? '';
42
+ } else if (obj.question) {
43
+ title = (obj.question.title ?? obj.title ?? '').replace(/<[^>]+>/g, '');
44
+ author = obj.author?.name ?? '';
45
+ url = obj.question.url ? `https://www.zhihu.com/question/${obj.question.id}` : '';
46
+ } else {
47
+ title = (obj.title ?? obj.name ?? '').replace(/<[^>]+>/g, '');
48
+ author = obj.author?.name ?? '';
49
+ url = obj.url ?? '';
50
+ }
51
+
52
+ if (!title) continue;
53
+
54
+ rows.push({
55
+ rank: rows.length + 1,
56
+ title: title.slice(0, 60),
57
+ author,
58
+ type: itemType.replace('search_result', 'result'),
59
+ url,
60
+ });
61
+ }
62
+
63
+ return rows;
64
+ },
65
+ });
package/src/engine.ts ADDED
@@ -0,0 +1,86 @@
1
+ /**
2
+ * CLI discovery: finds YAML/TS CLI definitions and registers them.
3
+ */
4
+
5
+ import * as fs from 'node:fs';
6
+ import * as path from 'node:path';
7
+ import yaml from 'js-yaml';
8
+ import { type CliCommand, type Arg, Strategy, registerCommand } from './registry.js';
9
+ import { executePipeline } from './pipeline.js';
10
+
11
+ export function discoverClis(...dirs: string[]): void {
12
+ for (const dir of dirs) {
13
+ if (!fs.existsSync(dir)) continue;
14
+ for (const site of fs.readdirSync(dir)) {
15
+ const siteDir = path.join(dir, site);
16
+ if (!fs.statSync(siteDir).isDirectory()) continue;
17
+ for (const file of fs.readdirSync(siteDir)) {
18
+ const filePath = path.join(siteDir, file);
19
+ if (file.endsWith('.yaml') || file.endsWith('.yml')) {
20
+ registerYamlCli(filePath, site);
21
+ }
22
+ }
23
+ }
24
+ }
25
+ }
26
+
27
+ function registerYamlCli(filePath: string, defaultSite: string): void {
28
+ try {
29
+ const raw = fs.readFileSync(filePath, 'utf-8');
30
+ const def = yaml.load(raw) as any;
31
+ if (!def || typeof def !== 'object') return;
32
+
33
+ const site = def.site ?? defaultSite;
34
+ const name = def.name ?? path.basename(filePath, path.extname(filePath));
35
+ const strategyStr = def.strategy ?? (def.browser === false ? 'public' : 'cookie');
36
+ const strategy = (Strategy as any)[strategyStr.toUpperCase()] ?? Strategy.COOKIE;
37
+ const browser = def.browser ?? (strategy !== Strategy.PUBLIC);
38
+
39
+ const args: Arg[] = [];
40
+ if (def.args && typeof def.args === 'object') {
41
+ for (const [argName, argDef] of Object.entries(def.args as Record<string, any>)) {
42
+ args.push({
43
+ name: argName,
44
+ type: argDef?.type ?? 'str',
45
+ default: argDef?.default,
46
+ required: argDef?.required ?? false,
47
+ help: argDef?.description ?? argDef?.help ?? '',
48
+ choices: argDef?.choices,
49
+ });
50
+ }
51
+ }
52
+
53
+ const cmd: CliCommand = {
54
+ site,
55
+ name,
56
+ description: def.description ?? '',
57
+ domain: def.domain,
58
+ strategy,
59
+ browser,
60
+ args,
61
+ columns: def.columns,
62
+ pipeline: def.pipeline,
63
+ timeoutSeconds: def.timeout,
64
+ source: filePath,
65
+ };
66
+
67
+ registerCommand(cmd);
68
+ } catch (err: any) {
69
+ process.stderr.write(`Warning: failed to load ${filePath}: ${err.message}\n`);
70
+ }
71
+ }
72
+
73
+ export async function executeCommand(
74
+ cmd: CliCommand,
75
+ page: any,
76
+ kwargs: Record<string, any>,
77
+ debug: boolean = false,
78
+ ): Promise<any> {
79
+ if (cmd.func) {
80
+ return cmd.func(page, kwargs, debug);
81
+ }
82
+ if (cmd.pipeline) {
83
+ return executePipeline(page, cmd.pipeline, { args: kwargs, debug });
84
+ }
85
+ throw new Error(`Command ${cmd.site}/${cmd.name} has no func or pipeline`);
86
+ }