@jackwener/opencli 1.6.6 → 1.6.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -1
- package/README.zh-CN.md +6 -2
- package/dist/clis/1688/assets.d.ts +42 -0
- package/dist/clis/1688/assets.js +204 -0
- package/dist/clis/1688/assets.test.d.ts +1 -0
- package/dist/clis/1688/assets.test.js +39 -0
- package/dist/clis/1688/download.d.ts +9 -0
- package/dist/clis/1688/download.js +76 -0
- package/dist/clis/1688/download.test.d.ts +1 -0
- package/dist/clis/1688/download.test.js +31 -0
- package/dist/clis/1688/shared.d.ts +10 -0
- package/dist/clis/1688/shared.js +43 -0
- package/dist/clis/linux-do/topic-content.d.ts +35 -0
- package/dist/clis/linux-do/topic-content.js +154 -0
- package/dist/clis/linux-do/topic-content.test.d.ts +1 -0
- package/dist/clis/linux-do/topic-content.test.js +59 -0
- package/dist/clis/linux-do/topic.yaml +1 -16
- package/dist/clis/xueqiu/groups.yaml +23 -0
- package/dist/clis/xueqiu/kline.yaml +65 -0
- package/dist/clis/xueqiu/watchlist.yaml +9 -9
- package/dist/src/analysis.d.ts +2 -0
- package/dist/src/analysis.js +6 -0
- package/dist/src/browser/cdp.js +96 -0
- package/dist/src/build-manifest.d.ts +3 -1
- package/dist/src/build-manifest.js +10 -7
- package/dist/src/build-manifest.test.js +8 -4
- package/dist/src/cli.d.ts +2 -1
- package/dist/src/cli.js +48 -46
- package/dist/src/commands/daemon.js +2 -10
- package/dist/src/diagnostic.d.ts +63 -0
- package/dist/src/diagnostic.js +247 -0
- package/dist/src/diagnostic.test.d.ts +1 -0
- package/dist/src/diagnostic.test.js +213 -0
- package/dist/src/discovery.js +7 -17
- package/dist/src/download/progress.js +7 -2
- package/dist/src/execution.js +25 -4
- package/dist/src/explore.d.ts +0 -2
- package/dist/src/explore.js +61 -38
- package/dist/src/extension-manifest-regression.test.js +0 -1
- package/dist/src/generate.d.ts +1 -1
- package/dist/src/generate.js +2 -3
- package/dist/src/package-paths.d.ts +8 -0
- package/dist/src/package-paths.js +41 -0
- package/dist/src/plugin-scaffold.js +1 -3
- package/dist/src/record.d.ts +1 -2
- package/dist/src/record.js +14 -52
- package/dist/src/synthesize.d.ts +0 -2
- package/dist/src/synthesize.js +8 -4
- package/package.json +1 -1
- package/scripts/postinstall.js +18 -71
- package/dist/cli-manifest.json +0 -17250
package/dist/src/record.d.ts
CHANGED
|
@@ -45,7 +45,6 @@ type RecordedCandidateKind = 'read' | 'write';
|
|
|
45
45
|
export interface RecordedCandidate {
|
|
46
46
|
kind: RecordedCandidateKind;
|
|
47
47
|
req: RecordedRequest;
|
|
48
|
-
score: number;
|
|
49
48
|
arrayResult: ReturnType<typeof findArrayPath> | null;
|
|
50
49
|
}
|
|
51
50
|
interface GeneratedRecordedCandidate {
|
|
@@ -70,7 +69,7 @@ export declare function createRecordedEntry(input: {
|
|
|
70
69
|
* for every JSON response. No URL pattern filter — captures everything.
|
|
71
70
|
*/
|
|
72
71
|
export declare function generateFullCaptureInterceptorJs(): string;
|
|
73
|
-
/** Analyze recorded requests into read and write candidates. */
|
|
72
|
+
/** Analyze recorded requests into read and write candidates, filtering out noise. */
|
|
74
73
|
export declare function analyzeRecordedRequests(requests: RecordedRequest[]): {
|
|
75
74
|
candidates: RecordedCandidate[];
|
|
76
75
|
};
|
package/dist/src/record.js
CHANGED
|
@@ -18,26 +18,11 @@ import chalk from 'chalk';
|
|
|
18
18
|
import yaml from 'js-yaml';
|
|
19
19
|
import { sendCommand } from './browser/daemon-client.js';
|
|
20
20
|
import { SEARCH_PARAMS, PAGINATION_PARAMS, FIELD_ROLES } from './constants.js';
|
|
21
|
-
import { urlToPattern, findArrayPath, inferCapabilityName, inferStrategy, detectAuthFromContent, classifyQueryParams, } from './analysis.js';
|
|
22
|
-
/** Keep the
|
|
23
|
-
function preferRecordedCandidate(
|
|
24
|
-
if (next.score > current.score)
|
|
25
|
-
return next;
|
|
26
|
-
if (next.score < current.score)
|
|
27
|
-
return current;
|
|
21
|
+
import { urlToPattern, findArrayPath, inferCapabilityName, inferStrategy, detectAuthFromContent, classifyQueryParams, isNoiseUrl, } from './analysis.js';
|
|
22
|
+
/** Keep the later candidate when multiple recordings share one bucket (prefer fresher data). */
|
|
23
|
+
function preferRecordedCandidate(_current, next) {
|
|
28
24
|
return next;
|
|
29
25
|
}
|
|
30
|
-
/** Apply shared endpoint score tweaks. */
|
|
31
|
-
function applyCommonEndpointScoreAdjustments(req, score) {
|
|
32
|
-
let adjusted = score;
|
|
33
|
-
if (req.url.includes('/api/'))
|
|
34
|
-
adjusted += 3;
|
|
35
|
-
if (req.url.match(/\/(track|log|analytics|beacon|pixel|stats|metric)/i))
|
|
36
|
-
adjusted -= 10;
|
|
37
|
-
if (req.url.match(/\/(ping|heartbeat|keep.?alive)/i))
|
|
38
|
-
adjusted -= 10;
|
|
39
|
-
return adjusted;
|
|
40
|
-
}
|
|
41
26
|
/** Build a candidate-level dedupe key. */
|
|
42
27
|
function getRecordedCandidateKey(candidate) {
|
|
43
28
|
return `${candidate.kind} ${getRecordedRequestKey(candidate.req)}`;
|
|
@@ -262,23 +247,6 @@ function generateReadRecordedJs() {
|
|
|
262
247
|
`;
|
|
263
248
|
}
|
|
264
249
|
// ── Analysis helpers ───────────────────────────────────────────────────────
|
|
265
|
-
function scoreRequest(req, arrayResult) {
|
|
266
|
-
let s = 0;
|
|
267
|
-
if (arrayResult) {
|
|
268
|
-
s += 10;
|
|
269
|
-
s += Math.min(arrayResult.items.length, 10);
|
|
270
|
-
// Bonus for detected semantic fields
|
|
271
|
-
const sample = arrayResult.items[0];
|
|
272
|
-
if (sample && typeof sample === 'object') {
|
|
273
|
-
const keys = Object.keys(sample).map(k => k.toLowerCase());
|
|
274
|
-
for (const aliases of Object.values(FIELD_ROLES)) {
|
|
275
|
-
if (aliases.some(a => keys.includes(a)))
|
|
276
|
-
s += 2;
|
|
277
|
-
}
|
|
278
|
-
}
|
|
279
|
-
}
|
|
280
|
-
return applyCommonEndpointScoreAdjustments(req, s);
|
|
281
|
-
}
|
|
282
250
|
/** Check whether one recorded request is safe to treat as a write candidate. */
|
|
283
251
|
function isWriteCandidate(req) {
|
|
284
252
|
return ['POST', 'PUT', 'PATCH'].includes(req.method)
|
|
@@ -290,25 +258,19 @@ function isWriteCandidate(req) {
|
|
|
290
258
|
&& typeof req.responseBody === 'object'
|
|
291
259
|
&& !Array.isArray(req.responseBody);
|
|
292
260
|
}
|
|
293
|
-
/**
|
|
294
|
-
function scoreWriteRequest(req) {
|
|
295
|
-
return applyCommonEndpointScoreAdjustments(req, 6);
|
|
296
|
-
}
|
|
297
|
-
/** Analyze recorded requests into read and write candidates. */
|
|
261
|
+
/** Analyze recorded requests into read and write candidates, filtering out noise. */
|
|
298
262
|
export function analyzeRecordedRequests(requests) {
|
|
299
263
|
const candidates = [];
|
|
300
264
|
for (const req of requests) {
|
|
265
|
+
if (isNoiseUrl(req.url))
|
|
266
|
+
continue;
|
|
301
267
|
const arrayResult = findArrayPath(req.responseBody);
|
|
302
268
|
if (isWriteCandidate(req)) {
|
|
303
|
-
|
|
304
|
-
if (score > 0)
|
|
305
|
-
candidates.push({ kind: 'write', req, score, arrayResult: null });
|
|
269
|
+
candidates.push({ kind: 'write', req, arrayResult: null });
|
|
306
270
|
continue;
|
|
307
271
|
}
|
|
308
272
|
if (arrayResult) {
|
|
309
|
-
|
|
310
|
-
if (score > 0)
|
|
311
|
-
candidates.push({ kind: 'read', req, score, arrayResult });
|
|
273
|
+
candidates.push({ kind: 'read', req, arrayResult });
|
|
312
274
|
}
|
|
313
275
|
}
|
|
314
276
|
return { candidates };
|
|
@@ -465,9 +427,9 @@ export function generateRecordedCandidates(site, pageUrl, requests) {
|
|
|
465
427
|
const current = deduped.get(key);
|
|
466
428
|
deduped.set(key, current ? preferRecordedCandidate(current, candidate) : candidate);
|
|
467
429
|
}
|
|
430
|
+
// Sort reads by array item count (richer data first), then take top 5
|
|
468
431
|
const selected = [...deduped.values()]
|
|
469
|
-
.
|
|
470
|
-
.sort((a, b) => b.score - a.score)
|
|
432
|
+
.sort((a, b) => (b.arrayResult?.items.length ?? 0) - (a.arrayResult?.items.length ?? 0))
|
|
471
433
|
.slice(0, 5);
|
|
472
434
|
const usedNames = new Set();
|
|
473
435
|
return selected.map((candidate) => {
|
|
@@ -636,13 +598,13 @@ function analyzeAndWrite(site, pageUrl, requests, outDir) {
|
|
|
636
598
|
// Generate candidate YAMLs (top 5)
|
|
637
599
|
const candidates = [];
|
|
638
600
|
const usedNames = new Set();
|
|
639
|
-
console.log(chalk.bold('\n Captured endpoints
|
|
640
|
-
for (const entry of analysis.candidates.sort((a, b) => b.
|
|
601
|
+
console.log(chalk.bold('\n Captured endpoints:\n'));
|
|
602
|
+
for (const entry of analysis.candidates.sort((a, b) => (b.arrayResult?.items.length ?? 0) - (a.arrayResult?.items.length ?? 0)).slice(0, 8)) {
|
|
641
603
|
const itemCount = entry.arrayResult?.items.length ?? 0;
|
|
642
604
|
const strategy = entry.kind === 'write'
|
|
643
605
|
? 'cookie'
|
|
644
606
|
: inferStrategy(detectAuthFromContent(entry.req.url, entry.req.responseBody));
|
|
645
|
-
const marker = entry.
|
|
607
|
+
const marker = entry.kind === 'write' ? chalk.magenta('✎') : itemCount > 5 ? chalk.green('★') : chalk.dim('·');
|
|
646
608
|
console.log(` ${marker} ${chalk.white(urlToPattern(entry.req.url))}` +
|
|
647
609
|
chalk.dim(` [${strategy}]`) +
|
|
648
610
|
(entry.kind === 'write'
|
|
@@ -664,7 +626,7 @@ function analyzeAndWrite(site, pageUrl, requests, outDir) {
|
|
|
664
626
|
console.log(chalk.dim(` → ${filePath}`));
|
|
665
627
|
}
|
|
666
628
|
if (candidates.length === 0) {
|
|
667
|
-
console.log(chalk.yellow(' No
|
|
629
|
+
console.log(chalk.yellow(' No candidates found.'));
|
|
668
630
|
console.log(chalk.dim(' Tip: make sure you triggered JSON API calls (open lists, search, scroll).'));
|
|
669
631
|
}
|
|
670
632
|
return {
|
package/dist/src/synthesize.d.ts
CHANGED
|
@@ -17,7 +17,6 @@ export interface SynthesizeCapability {
|
|
|
17
17
|
name: string;
|
|
18
18
|
description: string;
|
|
19
19
|
strategy: string;
|
|
20
|
-
confidence?: number;
|
|
21
20
|
endpoint?: string;
|
|
22
21
|
itemPath?: string | null;
|
|
23
22
|
recommendedColumns?: string[];
|
|
@@ -70,7 +69,6 @@ export interface SynthesizeCandidateSummary {
|
|
|
70
69
|
name: string;
|
|
71
70
|
path: string;
|
|
72
71
|
strategy: string;
|
|
73
|
-
confidence?: number;
|
|
74
72
|
}
|
|
75
73
|
export interface SynthesizeResult {
|
|
76
74
|
site: string;
|
package/dist/src/synthesize.js
CHANGED
|
@@ -13,7 +13,6 @@ export function synthesizeFromExplore(target, opts = {}) {
|
|
|
13
13
|
fs.mkdirSync(targetDir, { recursive: true });
|
|
14
14
|
const site = bundle.manifest.site;
|
|
15
15
|
const capabilities = (bundle.capabilities ?? [])
|
|
16
|
-
.sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0))
|
|
17
16
|
.slice(0, opts.top ?? 3);
|
|
18
17
|
const candidates = [];
|
|
19
18
|
for (const cap of capabilities) {
|
|
@@ -23,7 +22,7 @@ export function synthesizeFromExplore(target, opts = {}) {
|
|
|
23
22
|
const candidate = buildCandidateYaml(site, bundle.manifest, cap, endpoint);
|
|
24
23
|
const filePath = path.join(targetDir, `${candidate.name}.yaml`);
|
|
25
24
|
fs.writeFileSync(filePath, yaml.dump(candidate.yaml, { sortKeys: false, lineWidth: 120 }));
|
|
26
|
-
candidates.push({ name: candidate.name, path: filePath, strategy: cap.strategy
|
|
25
|
+
candidates.push({ name: candidate.name, path: filePath, strategy: cap.strategy });
|
|
27
26
|
}
|
|
28
27
|
const index = { site, target_url: bundle.manifest.target_url, generated_from: exploreDir, candidate_count: candidates.length, candidates };
|
|
29
28
|
fs.writeFileSync(path.join(targetDir, 'candidates.json'), JSON.stringify(index, null, 2));
|
|
@@ -32,7 +31,7 @@ export function synthesizeFromExplore(target, opts = {}) {
|
|
|
32
31
|
export function renderSynthesizeSummary(result) {
|
|
33
32
|
const lines = ['opencli synthesize: OK', `Site: ${result.site}`, `Source: ${result.explore_dir}`, `Candidates: ${result.candidate_count}`];
|
|
34
33
|
for (const c of result.candidates ?? [])
|
|
35
|
-
lines.push(` • ${c.name} (${c.strategy}
|
|
34
|
+
lines.push(` • ${c.name} (${c.strategy}) → ${c.path}`);
|
|
36
35
|
return lines.join('\n');
|
|
37
36
|
}
|
|
38
37
|
export function resolveExploreDir(target) {
|
|
@@ -61,7 +60,12 @@ function chooseEndpoint(cap, endpoints) {
|
|
|
61
60
|
if (match)
|
|
62
61
|
return match;
|
|
63
62
|
}
|
|
64
|
-
|
|
63
|
+
// Fallback: prefer endpoint with most data (item count + detected fields)
|
|
64
|
+
return [...endpoints].sort((a, b) => {
|
|
65
|
+
const aKey = (a.itemCount ?? 0) * 10 + Object.keys(a.detectedFields ?? {}).length;
|
|
66
|
+
const bKey = (b.itemCount ?? 0) * 10 + Object.keys(b.detectedFields ?? {}).length;
|
|
67
|
+
return bKey - aKey;
|
|
68
|
+
})[0];
|
|
65
69
|
}
|
|
66
70
|
// ── URL templating ─────────────────────────────────────────────────────────
|
|
67
71
|
function buildTemplatedUrl(rawUrl, cap, _endpoint) {
|
package/package.json
CHANGED
package/scripts/postinstall.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
* postinstall script —
|
|
4
|
+
* postinstall script — install shell completion files and print setup instructions.
|
|
5
5
|
*
|
|
6
6
|
* Detects the user's default shell and writes the completion script to the
|
|
7
|
-
* standard
|
|
8
|
-
*
|
|
7
|
+
* standard completion directory. For zsh and bash, the script prints manual
|
|
8
|
+
* instructions instead of modifying rc files (~/.zshrc, ~/.bashrc) — this
|
|
9
|
+
* avoids breaking multi-line shell commands and other fragile rc structures.
|
|
10
|
+
* Fish completions work automatically without rc changes.
|
|
9
11
|
*
|
|
10
12
|
* Supported shells: bash, zsh, fish.
|
|
11
13
|
*
|
|
@@ -13,7 +15,7 @@
|
|
|
13
15
|
* the main source tree) so that it can run without a build step.
|
|
14
16
|
*/
|
|
15
17
|
|
|
16
|
-
import { mkdirSync, writeFileSync, existsSync
|
|
18
|
+
import { mkdirSync, writeFileSync, existsSync } from 'node:fs';
|
|
17
19
|
import { join } from 'node:path';
|
|
18
20
|
import { homedir } from 'node:os';
|
|
19
21
|
|
|
@@ -69,54 +71,6 @@ function ensureDir(dir) {
|
|
|
69
71
|
}
|
|
70
72
|
}
|
|
71
73
|
|
|
72
|
-
/**
|
|
73
|
-
* Ensure fpath contains the custom completions directory in .zshrc.
|
|
74
|
-
*
|
|
75
|
-
* Key detail: the fpath line MUST appear BEFORE the first `compinit` call,
|
|
76
|
-
* otherwise compinit won't scan our completions directory. This is critical
|
|
77
|
-
* for oh-my-zsh users (source $ZSH/oh-my-zsh.sh calls compinit internally).
|
|
78
|
-
*/
|
|
79
|
-
function ensureZshFpath(completionsDir, zshrcPath) {
|
|
80
|
-
const fpathLine = `fpath=(${completionsDir} $fpath)`;
|
|
81
|
-
const autoloadLine = `autoload -Uz compinit && compinit`;
|
|
82
|
-
const marker = '# opencli completion';
|
|
83
|
-
|
|
84
|
-
if (!existsSync(zshrcPath)) {
|
|
85
|
-
writeFileSync(zshrcPath, `${marker}\n${fpathLine}\n${autoloadLine}\n`, 'utf8');
|
|
86
|
-
return;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
const content = readFileSync(zshrcPath, 'utf8');
|
|
90
|
-
|
|
91
|
-
// Already configured — nothing to do
|
|
92
|
-
if (content.includes(completionsDir)) {
|
|
93
|
-
return;
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
// Find the first line that triggers compinit (direct call or oh-my-zsh source)
|
|
97
|
-
const lines = content.split('\n');
|
|
98
|
-
let insertIdx = -1;
|
|
99
|
-
for (let i = 0; i < lines.length; i++) {
|
|
100
|
-
const trimmed = lines[i].trim();
|
|
101
|
-
// Skip comment-only lines
|
|
102
|
-
if (trimmed.startsWith('#')) continue;
|
|
103
|
-
if (/compinit/.test(trimmed) || /source\s+.*oh-my-zsh\.sh/.test(trimmed)) {
|
|
104
|
-
insertIdx = i;
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (insertIdx !== -1) {
|
|
110
|
-
// Insert fpath BEFORE the compinit / oh-my-zsh source line
|
|
111
|
-
lines.splice(insertIdx, 0, marker, fpathLine);
|
|
112
|
-
writeFileSync(zshrcPath, lines.join('\n'), 'utf8');
|
|
113
|
-
} else {
|
|
114
|
-
// No compinit found — append fpath + compinit at the end
|
|
115
|
-
let addition = `\n${marker}\n${fpathLine}\n${autoloadLine}\n`;
|
|
116
|
-
appendFileSync(zshrcPath, addition, 'utf8');
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
74
|
// ── Main ───────────────────────────────────────────────────────────────────
|
|
121
75
|
|
|
122
76
|
function main() {
|
|
@@ -147,35 +101,28 @@ function main() {
|
|
|
147
101
|
ensureDir(completionsDir);
|
|
148
102
|
writeFileSync(completionFile, ZSH_COMPLETION, 'utf8');
|
|
149
103
|
|
|
150
|
-
// Ensure fpath is set up in .zshrc
|
|
151
|
-
const zshrcPath = join(home, '.zshrc');
|
|
152
|
-
ensureZshFpath(completionsDir, zshrcPath);
|
|
153
|
-
|
|
154
104
|
console.log(`✓ Zsh completion installed to ${completionFile}`);
|
|
155
|
-
console.log(
|
|
105
|
+
console.log('');
|
|
106
|
+
console.log(' \x1b[1mTo enable, add these lines to your ~/.zshrc:\x1b[0m');
|
|
107
|
+
console.log(` fpath=(${completionsDir} $fpath)`);
|
|
108
|
+
console.log(' autoload -Uz compinit && compinit');
|
|
109
|
+
console.log('');
|
|
110
|
+
console.log(' If you already have compinit (oh-my-zsh, zinit, etc.), just add the fpath line \x1b[1mbefore\x1b[0m it.');
|
|
111
|
+
console.log(' Then restart your shell or run: \x1b[36mexec zsh\x1b[0m');
|
|
156
112
|
break;
|
|
157
113
|
}
|
|
158
114
|
case 'bash': {
|
|
159
|
-
// Try system-level first, fall back to user-level
|
|
160
115
|
const userCompDir = join(home, '.bash_completion.d');
|
|
161
116
|
const completionFile = join(userCompDir, 'opencli');
|
|
162
117
|
ensureDir(userCompDir);
|
|
163
118
|
writeFileSync(completionFile, BASH_COMPLETION, 'utf8');
|
|
164
119
|
|
|
165
|
-
// Ensure .bashrc sources the completion directory
|
|
166
|
-
const bashrcPath = join(home, '.bashrc');
|
|
167
|
-
if (existsSync(bashrcPath)) {
|
|
168
|
-
const content = readFileSync(bashrcPath, 'utf8');
|
|
169
|
-
if (!content.includes('.bash_completion.d/opencli')) {
|
|
170
|
-
appendFileSync(bashrcPath,
|
|
171
|
-
`\n# opencli completion\n[ -f "${completionFile}" ] && source "${completionFile}"\n`,
|
|
172
|
-
'utf8'
|
|
173
|
-
);
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
|
|
177
120
|
console.log(`✓ Bash completion installed to ${completionFile}`);
|
|
178
|
-
console.log(
|
|
121
|
+
console.log('');
|
|
122
|
+
console.log(' \x1b[1mTo enable, add this line to your ~/.bashrc:\x1b[0m');
|
|
123
|
+
console.log(` [ -f "${completionFile}" ] && source "${completionFile}"`);
|
|
124
|
+
console.log('');
|
|
125
|
+
console.log(' Then restart your shell or run: \x1b[36msource ~/.bashrc\x1b[0m');
|
|
179
126
|
break;
|
|
180
127
|
}
|
|
181
128
|
case 'fish': {
|