@jackwener/opencli 0.7.8 → 0.7.9

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.
@@ -622,6 +622,45 @@
622
622
  "url"
623
623
  ]
624
624
  },
625
+ {
626
+ "site": "github",
627
+ "name": "search",
628
+ "description": "Search GitHub repositories",
629
+ "strategy": "public",
630
+ "browser": false,
631
+ "args": [
632
+ {
633
+ "name": "keyword",
634
+ "type": "str",
635
+ "required": true,
636
+ "help": "Search keyword"
637
+ },
638
+ {
639
+ "name": "sort",
640
+ "type": "str",
641
+ "default": "stars",
642
+ "required": false,
643
+ "help": "Sort by: stars, forks, updated"
644
+ },
645
+ {
646
+ "name": "limit",
647
+ "type": "int",
648
+ "default": 20,
649
+ "required": false,
650
+ "help": "Number of results"
651
+ }
652
+ ],
653
+ "type": "ts",
654
+ "modulePath": "github/search.js",
655
+ "domain": "github.com",
656
+ "columns": [
657
+ "rank",
658
+ "name",
659
+ "stars",
660
+ "language",
661
+ "description"
662
+ ]
663
+ },
625
664
  {
626
665
  "site": "hackernews",
627
666
  "name": "top",
@@ -1877,7 +1916,7 @@
1877
1916
  {
1878
1917
  "site": "twitter",
1879
1918
  "name": "timeline",
1880
- "description": "Twitter Home Timeline",
1919
+ "description": "Fetch Twitter Home Timeline",
1881
1920
  "strategy": "cookie",
1882
1921
  "browser": true,
1883
1922
  "args": [
@@ -1893,8 +1932,15 @@
1893
1932
  "modulePath": "twitter/timeline.js",
1894
1933
  "domain": "x.com",
1895
1934
  "columns": [
1896
- "responseType",
1897
- "first"
1935
+ "id",
1936
+ "author",
1937
+ "text",
1938
+ "likes",
1939
+ "retweets",
1940
+ "replies",
1941
+ "views",
1942
+ "created_at",
1943
+ "url"
1898
1944
  ]
1899
1945
  },
1900
1946
  {
@@ -2244,6 +2290,27 @@
2244
2290
  ],
2245
2291
  "type": "yaml"
2246
2292
  },
2293
+ {
2294
+ "site": "xiaohongshu",
2295
+ "name": "me",
2296
+ "description": "我的小红书个人信息",
2297
+ "strategy": "cookie",
2298
+ "browser": true,
2299
+ "args": [],
2300
+ "type": "ts",
2301
+ "modulePath": "xiaohongshu/me.js",
2302
+ "domain": "www.xiaohongshu.com",
2303
+ "columns": [
2304
+ "nickname",
2305
+ "red_id",
2306
+ "location",
2307
+ "profession",
2308
+ "fans",
2309
+ "follows",
2310
+ "likes_collected",
2311
+ "notes"
2312
+ ]
2313
+ },
2247
2314
  {
2248
2315
  "site": "xiaohongshu",
2249
2316
  "name": "notifications",
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,20 @@
1
+ import { cli, Strategy } from '../../registry.js';
2
+ cli({
3
+ site: 'github', name: 'search', description: 'Search GitHub repositories', domain: 'github.com', strategy: Strategy.PUBLIC, browser: false,
4
+ args: [
5
+ { name: 'keyword', required: true, help: 'Search keyword' },
6
+ { name: 'sort', default: 'stars', help: 'Sort by: stars, forks, updated' },
7
+ { name: 'limit', type: 'int', default: 20, help: 'Number of results' },
8
+ ],
9
+ columns: ['rank', 'name', 'stars', 'language', 'description'],
10
+ func: async (_page, kwargs) => {
11
+ const { keyword, sort = 'stars', limit = 20 } = kwargs;
12
+ const resp = await fetch(`https://api.github.com/search/repositories?${new URLSearchParams({ q: keyword, sort, order: 'desc', per_page: String(Math.min(Number(limit), 100)) })}`, {
13
+ headers: { Accept: 'application/vnd.github.v3+json', 'User-Agent': 'opencli/0.1' },
14
+ });
15
+ const data = await resp.json();
16
+ return (data.items ?? []).slice(0, Number(limit)).map((item, i) => ({
17
+ rank: i + 1, name: item.full_name, stars: item.stargazers_count, language: item.language ?? '', description: (item.description ?? '').slice(0, 80),
18
+ }));
19
+ },
20
+ });
@@ -0,0 +1,27 @@
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
+ import './bilibili/search.js';
7
+ import './bilibili/me.js';
8
+ import './bilibili/favorite.js';
9
+ import './bilibili/history.js';
10
+ import './bilibili/feed.js';
11
+ import './bilibili/user-videos.js';
12
+ import './bilibili/ranking.js';
13
+ import './bilibili/dynamic.js';
14
+ import './github/search.js';
15
+ import './zhihu/question.js';
16
+ import './xiaohongshu/search.js';
17
+ import './xiaohongshu/user.js';
18
+ import './bbc/news.js';
19
+ import './weibo/hot.js';
20
+ import './boss/search.js';
21
+ import './yahoo-finance/quote.js';
22
+ import './reuters/search.js';
23
+ import './smzdm/search.js';
24
+ import './ctrip/search.js';
25
+ import './youtube/search.js';
26
+ import './twitter/search.js';
27
+ import './twitter/profile.js';
@@ -0,0 +1,41 @@
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
+ // bilibili
7
+ import './bilibili/search.js';
8
+ import './bilibili/me.js';
9
+ import './bilibili/favorite.js';
10
+ import './bilibili/history.js';
11
+ import './bilibili/feed.js';
12
+ import './bilibili/user-videos.js';
13
+ import './bilibili/ranking.js';
14
+ import './bilibili/dynamic.js';
15
+ // github
16
+ import './github/search.js';
17
+ // zhihu
18
+ import './zhihu/question.js';
19
+ // xiaohongshu
20
+ import './xiaohongshu/search.js';
21
+ import './xiaohongshu/user.js';
22
+ // bbc
23
+ import './bbc/news.js';
24
+ // weibo
25
+ import './weibo/hot.js';
26
+ // boss
27
+ import './boss/search.js';
28
+ // yahoo-finance
29
+ import './yahoo-finance/quote.js';
30
+ // reuters
31
+ import './reuters/search.js';
32
+ // smzdm
33
+ import './smzdm/search.js';
34
+ // ctrip
35
+ import './ctrip/search.js';
36
+ // youtube
37
+ import './youtube/search.js';
38
+ // twitter
39
+ import './twitter/search.js';
40
+ import './twitter/profile.js';
41
+ // reddit
@@ -1,47 +1,186 @@
1
1
  import { cli, Strategy } from '../../registry.js';
2
+ // ── Twitter GraphQL constants ──────────────────────────────────────────
3
+ const BEARER_TOKEN = 'AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA';
4
+ const HOME_TIMELINE_QUERY_ID = 'c-CzHF1LboFilMpsx4ZCrQ';
5
+ const FEATURES = {
6
+ rweb_video_screen_enabled: false,
7
+ profile_label_improvements_pcf_label_in_post_enabled: true,
8
+ rweb_tipjar_consumption_enabled: true,
9
+ verified_phone_label_enabled: false,
10
+ creator_subscriptions_tweet_preview_api_enabled: true,
11
+ responsive_web_graphql_timeline_navigation_enabled: true,
12
+ responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
13
+ premium_content_api_read_enabled: false,
14
+ communities_web_enable_tweet_community_results_fetch: true,
15
+ c9s_tweet_anatomy_moderator_badge_enabled: true,
16
+ responsive_web_grok_analyze_button_fetch_trends_enabled: false,
17
+ responsive_web_grok_analyze_post_followups_enabled: true,
18
+ responsive_web_jetfuel_frame: false,
19
+ responsive_web_grok_share_attachment_enabled: true,
20
+ articles_preview_enabled: true,
21
+ responsive_web_edit_tweet_api_enabled: true,
22
+ graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
23
+ view_counts_everywhere_api_enabled: true,
24
+ longform_notetweets_consumption_enabled: true,
25
+ responsive_web_twitter_article_tweet_consumption_enabled: true,
26
+ tweet_awards_web_tipping_enabled: false,
27
+ responsive_web_grok_show_grok_translated_post: false,
28
+ responsive_web_grok_analysis_button_from_backend: false,
29
+ creator_subscriptions_quote_tweet_preview_enabled: false,
30
+ freedom_of_speech_not_reach_fetch_enabled: true,
31
+ standardized_nudges_misinfo: true,
32
+ tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
33
+ longform_notetweets_rich_text_read_enabled: true,
34
+ longform_notetweets_inline_media_enabled: true,
35
+ responsive_web_grok_image_annotation_enabled: true,
36
+ responsive_web_enhance_cards_enabled: false,
37
+ };
38
+ function buildHomeTimelineUrl(count, cursor) {
39
+ const vars = {
40
+ count,
41
+ includePromotedContent: false,
42
+ latestControlAvailable: true,
43
+ requestContext: 'launch',
44
+ withCommunity: true,
45
+ };
46
+ if (cursor)
47
+ vars.cursor = cursor;
48
+ return `/i/api/graphql/${HOME_TIMELINE_QUERY_ID}/HomeTimeline`
49
+ + `?variables=${encodeURIComponent(JSON.stringify(vars))}`
50
+ + `&features=${encodeURIComponent(JSON.stringify(FEATURES))}`;
51
+ }
52
+ function extractTweet(result, seen) {
53
+ if (!result)
54
+ return null;
55
+ const tw = result.tweet || result;
56
+ const l = tw.legacy || {};
57
+ if (!tw.rest_id || seen.has(tw.rest_id))
58
+ return null;
59
+ seen.add(tw.rest_id);
60
+ const u = tw.core?.user_results?.result;
61
+ const screenName = u?.legacy?.screen_name || u?.core?.screen_name || 'unknown';
62
+ const noteText = tw.note_tweet?.note_tweet_results?.result?.text;
63
+ const views = tw.views?.count ? parseInt(tw.views.count, 10) : 0;
64
+ return {
65
+ id: tw.rest_id,
66
+ author: screenName,
67
+ text: noteText || l.full_text || '',
68
+ likes: l.favorite_count || 0,
69
+ retweets: l.retweet_count || 0,
70
+ replies: l.reply_count || 0,
71
+ views,
72
+ created_at: l.created_at || '',
73
+ url: `https://x.com/${screenName}/status/${tw.rest_id}`,
74
+ };
75
+ }
76
+ function parseHomeTimeline(data, seen) {
77
+ const tweets = [];
78
+ let nextCursor = null;
79
+ const instructions = data?.data?.home?.home_timeline_urt?.instructions || [];
80
+ for (const inst of instructions) {
81
+ for (const entry of inst.entries || []) {
82
+ const c = entry.content;
83
+ // Cursor entries
84
+ if (c?.entryType === 'TimelineTimelineCursor' || c?.__typename === 'TimelineTimelineCursor') {
85
+ if (c.cursorType === 'Bottom')
86
+ nextCursor = c.value;
87
+ continue;
88
+ }
89
+ if (entry.entryId?.startsWith('cursor-bottom-')) {
90
+ nextCursor = c?.value || nextCursor;
91
+ continue;
92
+ }
93
+ // Single tweet entry
94
+ const tweetResult = c?.itemContent?.tweet_results?.result;
95
+ if (tweetResult) {
96
+ // Skip promoted content
97
+ if (c?.itemContent?.promotedMetadata)
98
+ continue;
99
+ const tw = extractTweet(tweetResult, seen);
100
+ if (tw)
101
+ tweets.push(tw);
102
+ continue;
103
+ }
104
+ // Conversation module (grouped tweets)
105
+ for (const item of c?.items || []) {
106
+ const nested = item.item?.itemContent?.tweet_results?.result;
107
+ if (nested) {
108
+ if (item.item?.itemContent?.promotedMetadata)
109
+ continue;
110
+ const tw = extractTweet(nested, seen);
111
+ if (tw)
112
+ tweets.push(tw);
113
+ }
114
+ }
115
+ }
116
+ }
117
+ return { tweets, nextCursor };
118
+ }
119
+ // ── CLI definition ────────────────────────────────────────────────────
2
120
  cli({
3
121
  site: 'twitter',
4
122
  name: 'timeline',
5
- description: 'Twitter Home Timeline',
123
+ description: 'Fetch Twitter Home Timeline',
6
124
  domain: 'x.com',
7
125
  strategy: Strategy.COOKIE,
126
+ browser: true,
8
127
  args: [
9
128
  { name: 'limit', type: 'int', default: 20 },
10
129
  ],
11
- columns: ['responseType', 'first'],
130
+ columns: ['id', 'author', 'text', 'likes', 'retweets', 'replies', 'views', 'created_at', 'url'],
12
131
  func: async (page, kwargs) => {
13
- await page.goto('https://x.com/home');
14
- await page.wait(5);
15
- // Inject the fetch interceptor manually to see exactly what happens
16
- await page.evaluate(`
17
- () => {
18
- window.__intercept_data = [];
19
- const origFetch = window.fetch;
20
- window.fetch = async function(...args) {
21
- let u = typeof args[0] === 'string' ? args[0] : (args[0] && args[0].url) || '';
22
- const res = await origFetch.apply(this, args);
23
- setTimeout(async () => {
24
- try {
25
- if (u.includes('HomeTimeline')) {
26
- const clone = res.clone();
27
- const j = await clone.json();
28
- window.__intercept_data.push(j);
29
- }
30
- } catch(e) {}
31
- }, 0);
32
- return res;
33
- };
34
- }
35
- `);
36
- // trigger scroll
37
- for (let i = 0; i < 3; i++) {
38
- await page.evaluate('() => window.scrollTo(0, document.body.scrollHeight)');
39
- await page.wait(2);
132
+ const limit = kwargs.limit || 20;
133
+ // Navigate to x.com for cookie context
134
+ await page.goto('https://x.com');
135
+ await page.wait(3);
136
+ // Extract CSRF token
137
+ const ct0 = await page.evaluate(`() => {
138
+ return document.cookie.split(';').map(c=>c.trim()).find(c=>c.startsWith('ct0='))?.split('=')[1] || null;
139
+ }`);
140
+ if (!ct0)
141
+ throw new Error('Not logged into x.com (no ct0 cookie)');
142
+ // Dynamically resolve queryId
143
+ const queryId = await page.evaluate(`async () => {
144
+ try {
145
+ const ghResp = await fetch('https://raw.githubusercontent.com/fa0311/twitter-openapi/refs/heads/main/src/config/placeholder.json');
146
+ if (ghResp.ok) {
147
+ const data = await ghResp.json();
148
+ const entry = data['HomeTimeline'];
149
+ if (entry && entry.queryId) return entry.queryId;
40
150
  }
41
- // extract
42
- const data = await page.evaluate('() => window.__intercept_data');
43
- if (!data || data.length === 0)
44
- return [{ responseType: 'no data captured' }];
45
- return [{ responseType: `captured ${data.length} responses`, first: JSON.stringify(data[0]).substring(0, 300) }];
46
- }
151
+ } catch {}
152
+ return null;
153
+ }`) || HOME_TIMELINE_QUERY_ID;
154
+ // Build auth headers
155
+ const headers = JSON.stringify({
156
+ 'Authorization': `Bearer ${decodeURIComponent(BEARER_TOKEN)}`,
157
+ 'X-Csrf-Token': ct0,
158
+ 'X-Twitter-Auth-Type': 'OAuth2Session',
159
+ 'X-Twitter-Active-User': 'yes',
160
+ });
161
+ // Paginate — fetch in browser, parse in TypeScript
162
+ const allTweets = [];
163
+ const seen = new Set();
164
+ let cursor = null;
165
+ for (let i = 0; i < 5 && allTweets.length < limit; i++) {
166
+ const fetchCount = Math.min(40, limit - allTweets.length + 5); // over-fetch slightly for promoted filtering
167
+ const apiUrl = buildHomeTimelineUrl(fetchCount, cursor)
168
+ .replace(HOME_TIMELINE_QUERY_ID, queryId);
169
+ const data = await page.evaluate(`async () => {
170
+ const r = await fetch("${apiUrl}", { headers: ${headers}, credentials: 'include' });
171
+ return r.ok ? await r.json() : { error: r.status };
172
+ }`);
173
+ if (data?.error) {
174
+ if (allTweets.length === 0)
175
+ throw new Error(`HTTP ${data.error}: Failed to fetch timeline. queryId may have expired.`);
176
+ break;
177
+ }
178
+ const { tweets, nextCursor } = parseHomeTimeline(data, seen);
179
+ allTweets.push(...tweets);
180
+ if (!nextCursor || nextCursor === cursor)
181
+ break;
182
+ cursor = nextCursor;
183
+ }
184
+ return allTweets.slice(0, limit);
185
+ },
47
186
  });
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Xiaohongshu me — read self profile info.
3
+ *
4
+ * Two-step navigation:
5
+ * 1. /explore → get user_id from Pinia user store
6
+ * 2. /user/profile/{user_id} → poll until userPageData loads, read full profile
7
+ */
8
+ import { cli, Strategy } from '../../registry.js';
9
+ cli({
10
+ site: 'xiaohongshu',
11
+ name: 'me',
12
+ description: '我的小红书个人信息',
13
+ domain: 'www.xiaohongshu.com',
14
+ strategy: Strategy.COOKIE,
15
+ args: [],
16
+ columns: ['nickname', 'red_id', 'location', 'profession', 'fans', 'follows', 'likes_collected', 'notes'],
17
+ func: async (page) => {
18
+ // Step 1: Navigate to /explore to get user_id
19
+ await page.goto('https://www.xiaohongshu.com/explore');
20
+ await page.wait(2);
21
+ const userId = await page.evaluate(`
22
+ (() => {
23
+ const app = document.querySelector('#app')?.__vue_app__;
24
+ const pinia = app?.config?.globalProperties?.$pinia;
25
+ const store = pinia?._s?.get('user');
26
+ const u = store?.$state?.userInfo || {};
27
+ return u.user_id || u.userId || '';
28
+ })()
29
+ `);
30
+ if (!userId)
31
+ return [{ error: 'Not logged in or user_id not found' }];
32
+ // Step 2: Navigate to real profile page for full data
33
+ await page.goto(`https://www.xiaohongshu.com/user/profile/${userId}`);
34
+ await page.wait(3);
35
+ const data = await page.evaluate(`
36
+ (async () => {
37
+ // Poll for userPageData to be populated (profile page loads async)
38
+ const deadline = Date.now() + 8000;
39
+ let pd = null;
40
+ while (Date.now() < deadline) {
41
+ const app = document.querySelector('#app')?.__vue_app__;
42
+ const pinia = app?.config?.globalProperties?.$pinia;
43
+ const store = pinia?._s?.get('user');
44
+ pd = store?.$state?.userPageData;
45
+ if (pd?.interactions?.length > 0) break;
46
+ await new Promise(r => setTimeout(r, 500));
47
+ }
48
+
49
+ if (!pd?.interactions?.length) {
50
+ return { error: 'Profile data did not load' };
51
+ }
52
+
53
+ const basic = pd.basicInfo || {};
54
+ const interactions = pd.interactions || [];
55
+ const tags = pd.tags || [];
56
+
57
+ const getCount = (type) => {
58
+ const item = interactions.find(i => i.type === type);
59
+ return item ? item.count : '0';
60
+ };
61
+ const getTag = (tagType) => {
62
+ const item = tags.find(t => t.tagType === tagType);
63
+ return item ? item.name : '';
64
+ };
65
+
66
+ const app2 = document.querySelector('#app')?.__vue_app__;
67
+ const store2 = app2?.config?.globalProperties?.$pinia?._s?.get('user');
68
+ const noteCount = (store2?.$state?.notes || []).length;
69
+
70
+ return {
71
+ nickname: basic.nickname || '',
72
+ red_id: basic.redId || '',
73
+ location: getTag('location') || basic.ipLocation || '',
74
+ profession: getTag('profession') || '',
75
+ fans: getCount('fans'),
76
+ follows: getCount('follows'),
77
+ likes_collected: getCount('interaction'),
78
+ notes: noteCount,
79
+ };
80
+ })()
81
+ `);
82
+ if (!data || data.error)
83
+ return [];
84
+ return [data];
85
+ },
86
+ });
package/dist/main.js CHANGED
File without changes
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,7 @@
1
+ import { render, evalExpr } from './pipeline/template.js';
2
+ const ctx = { item: { first: 'X', second: 'Y' } };
3
+ console.log('evalExpr item.first:', JSON.stringify(evalExpr('item.first', ctx)));
4
+ console.log('evalExpr item.second:', JSON.stringify(evalExpr('item.second', ctx)));
5
+ const template = '$' + '{{ item.first }}-$' + '{{ item.second }}';
6
+ console.log('template:', JSON.stringify(template));
7
+ console.log('render result:', JSON.stringify(render(template, ctx)));
@@ -0,0 +1 @@
1
+ export declare function promoteCandidate(opts: any): any;
@@ -0,0 +1,3 @@
1
+ /** Promote verified drafts. */
2
+ import { registerCandidates } from './register.js';
3
+ export function promoteCandidate(opts) { return registerCandidates(opts); }
@@ -0,0 +1,2 @@
1
+ /** Register candidates, promote, scaffold, generate — stubs with exports. */
2
+ export declare function registerCandidates(opts: any): any;
@@ -0,0 +1,2 @@
1
+ /** Register candidates, promote, scaffold, generate — stubs with exports. */
2
+ export function registerCandidates(opts) { return { ok: true, count: 0 }; }
@@ -0,0 +1,2 @@
1
+ /** Scaffold a draft CLI from endpoint evidence. */
2
+ export declare function scaffoldCli(opts: any): any;
@@ -0,0 +1,2 @@
1
+ /** Scaffold a draft CLI from endpoint evidence. */
2
+ export function scaffoldCli(opts) { return { ok: true }; }
package/dist/setup.js CHANGED
@@ -159,12 +159,18 @@ export async function runSetup(opts = {}) {
159
159
  console.log(` ${chalk.green('✓')} Browser connected in ${(result.durationMs / 1000).toFixed(1)}s`);
160
160
  }
161
161
  else {
162
+ console.log(` ${chalk.green('✓')} Token saved successfully.`);
162
163
  console.log(` ${chalk.yellow('!')} Browser connectivity test failed: ${result.error ?? 'unknown'}`);
163
- console.log(chalk.dim(' Make sure Chrome is running with the extension enabled.'));
164
+ console.log(chalk.dim(' Token configuration is complete. To use opencli, make sure Chrome'));
165
+ console.log(chalk.dim(' is running with the Playwright MCP Bridge extension enabled.'));
166
+ console.log(chalk.dim(` Run ${chalk.bold('opencli doctor --live')} to re-test connectivity.`));
164
167
  }
165
168
  }
166
169
  catch {
167
- console.log(` ${chalk.yellow('!')} Could not verify connectivity (Chrome may not be running)`);
170
+ console.log(` ${chalk.green('')} Token saved successfully.`);
171
+ console.log(` ${chalk.yellow('!')} Browser connectivity test skipped (Chrome may not be running).`);
172
+ console.log(chalk.dim(' Token configuration is complete. Start Chrome to begin using opencli.'));
173
+ console.log(chalk.dim(` Run ${chalk.bold('opencli doctor --live')} to re-test connectivity.`));
168
174
  }
169
175
  console.log();
170
176
  }
@@ -0,0 +1,2 @@
1
+ /** Smoke testing, verification, register, promote, explore, synthesize, scaffold, generate — stubs. */
2
+ export declare function runSmoke(cmd: any, page: any, args?: any): Promise<any>;
package/dist/smoke.js ADDED
@@ -0,0 +1,2 @@
1
+ /** Smoke testing, verification, register, promote, explore, synthesize, scaffold, generate — stubs. */
2
+ export async function runSmoke(cmd, page, args) { return { ok: true, result: null }; }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jackwener/opencli",
3
- "version": "0.7.8",
3
+ "version": "0.7.9",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },