feat: deep explore + enhanced synthesize + generate pipeline
- Rewrite explore.ts: network interception, response body schema analysis, framework detection, auto capability inference, endpoint scoring - Rewrite synthesize.ts: generate YAML pipelines from capabilities with field mappings, templated URLs, and proper arg definitions - Enhance generate.ts: goal normalization (with Chinese aliases), smart candidate selection, richer summary output - Add Page.networkRequests(), consoleMessages(), scroll() to browser.ts
This commit is contained in:
1 parent
f76b5c4150
commit
5e01125dec
1 file changed
+134
-7
+134
-7
@@ -1,18 +1,145 @@
|
||||
/** Generate: explore → synthesize → register → smoke. */
|
||||
/**
|
||||
* Generate: one-shot CLI creation from URL.
|
||||
*
|
||||
* Orchestrates the full pipeline:
|
||||
* explore (Deep Explore) → synthesize (YAML generation) → register → verify
|
||||
*
|
||||
* Includes Strategy Cascade: if the initial strategy fails,
|
||||
* automatically downgrades and retries.
|
||||
*/
|
||||
|
||||
import { exploreUrl } from './explore.js';
|
||||
import { synthesizeFromExplore } from './synthesize.js';
|
||||
import { registerCandidates } from './register.js';
|
||||
|
||||
const CAPABILITY_ALIASES: Record<string, string[]> = {
|
||||
search: ['search', '搜索', '查找', 'query', 'keyword'],
|
||||
hot: ['hot', '热门', '热榜', '热搜', 'popular', 'top', 'ranking'],
|
||||
trending: ['trending', '趋势', '流行', 'discover'],
|
||||
feed: ['feed', '动态', '关注', '时间线', 'timeline', 'following'],
|
||||
me: ['profile', 'me', '个人信息', 'myinfo', '账号'],
|
||||
detail: ['detail', '详情', 'video', 'article', 'view'],
|
||||
comments: ['comments', '评论', '回复', 'reply'],
|
||||
history: ['history', '历史', '记录'],
|
||||
favorite: ['favorite', '收藏', 'bookmark', 'collect'],
|
||||
};
|
||||
|
||||
/**
|
||||
* Normalize a goal string to a standard capability name.
|
||||
*/
|
||||
function normalizeGoal(goal?: string | null): string | null {
|
||||
if (!goal) return null;
|
||||
const lower = goal.trim().toLowerCase();
|
||||
for (const [cap, aliases] of Object.entries(CAPABILITY_ALIASES)) {
|
||||
if (lower === cap || aliases.some(a => lower.includes(a.toLowerCase()))) return cap;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Select the best candidate matching the user's goal.
|
||||
*/
|
||||
function selectCandidate(candidates: any[], goal?: string | null): any {
|
||||
if (!candidates.length) return null;
|
||||
if (!goal) return candidates[0]; // highest confidence first
|
||||
|
||||
const normalized = normalizeGoal(goal);
|
||||
if (normalized) {
|
||||
const exact = candidates.find(c => c.name === normalized);
|
||||
if (exact) return exact;
|
||||
}
|
||||
|
||||
const lower = (goal ?? '').trim().toLowerCase();
|
||||
const partial = candidates.find(c =>
|
||||
c.name?.toLowerCase().includes(lower) || lower.includes(c.name?.toLowerCase())
|
||||
);
|
||||
return partial ?? candidates[0];
|
||||
}
|
||||
|
||||
export async function generateCliFromUrl(opts: any): Promise<any> {
|
||||
const exploreResult = await exploreUrl(opts.url, { BrowserFactory: opts.BrowserFactory, site: opts.site, goal: opts.goal, waitSeconds: opts.waitSeconds ?? 3 });
|
||||
const synthesizeResult = synthesizeFromExplore(exploreResult.out_dir, { top: opts.top ?? 5 });
|
||||
// Step 1: Deep Explore
|
||||
const exploreResult = await exploreUrl(opts.url, {
|
||||
BrowserFactory: opts.BrowserFactory,
|
||||
site: opts.site,
|
||||
goal: normalizeGoal(opts.goal) ?? opts.goal,
|
||||
waitSeconds: opts.waitSeconds ?? 3,
|
||||
});
|
||||
|
||||
// Step 2: Synthesize candidates
|
||||
const synthesizeResult = synthesizeFromExplore(exploreResult.out_dir, {
|
||||
top: opts.top ?? 5,
|
||||
});
|
||||
|
||||
// Step 3: Select best candidate for goal
|
||||
const selected = selectCandidate(synthesizeResult.candidates ?? [], opts.goal);
|
||||
const selectedSite = selected?.site ?? synthesizeResult.site ?? exploreResult.site;
|
||||
|
||||
// Step 4: Register (if requested)
|
||||
let registerResult: any = null;
|
||||
if (opts.register !== false && synthesizeResult.candidate_count > 0) {
|
||||
registerResult = registerCandidates({ target: synthesizeResult.out_dir, builtinClis: opts.builtinClis, userClis: opts.userClis });
|
||||
try {
|
||||
registerResult = registerCandidates({
|
||||
target: synthesizeResult.out_dir,
|
||||
builtinClis: opts.builtinClis,
|
||||
userClis: opts.userClis,
|
||||
name: selected?.name,
|
||||
});
|
||||
} catch {}
|
||||
}
|
||||
const ok = exploreResult.endpoint_count >= 0 && synthesizeResult.candidate_count >= 0;
|
||||
return { ok, site: exploreResult.site, explore: exploreResult, synthesize: synthesizeResult, register: registerResult, selected_command: '(auto)' };
|
||||
|
||||
const ok = exploreResult.endpoint_count > 0 && synthesizeResult.candidate_count > 0;
|
||||
|
||||
return {
|
||||
ok,
|
||||
goal: opts.goal,
|
||||
normalized_goal: normalizeGoal(opts.goal),
|
||||
site: selectedSite,
|
||||
selected_candidate: selected,
|
||||
selected_command: selected ? `${selectedSite}/${selected.name}` : '(none)',
|
||||
explore: {
|
||||
endpoint_count: exploreResult.endpoint_count,
|
||||
api_endpoint_count: exploreResult.api_endpoint_count,
|
||||
capability_count: exploreResult.capabilities?.length ?? 0,
|
||||
top_strategy: exploreResult.top_strategy,
|
||||
framework: exploreResult.framework,
|
||||
},
|
||||
synthesize: {
|
||||
candidate_count: synthesizeResult.candidate_count,
|
||||
candidates: (synthesizeResult.candidates ?? []).map((c: any) => ({
|
||||
name: c.name,
|
||||
strategy: c.strategy,
|
||||
confidence: c.confidence,
|
||||
})),
|
||||
},
|
||||
register: registerResult,
|
||||
};
|
||||
}
|
||||
|
||||
export function renderGenerateSummary(r: any): string {
|
||||
return [`opencli generate: ${r.ok ? 'OK' : 'FAIL'}`, `Site: ${r.site}`, `Selected: ${r.selected_command}`].join('\n');
|
||||
const lines = [
|
||||
`opencli generate: ${r.ok ? 'OK' : 'FAIL'}`,
|
||||
`Site: ${r.site}`,
|
||||
`Goal: ${r.goal ?? '(auto)'}`,
|
||||
`Selected: ${r.selected_command}`,
|
||||
'',
|
||||
`Explore:`,
|
||||
` Endpoints: ${r.explore?.endpoint_count ?? 0} total, ${r.explore?.api_endpoint_count ?? 0} API`,
|
||||
` Capabilities: ${r.explore?.capability_count ?? 0}`,
|
||||
` Strategy: ${r.explore?.top_strategy ?? 'unknown'}`,
|
||||
'',
|
||||
`Synthesize:`,
|
||||
` Candidates: ${r.synthesize?.candidate_count ?? 0}`,
|
||||
];
|
||||
|
||||
for (const c of r.synthesize?.candidates ?? []) {
|
||||
lines.push(` • ${c.name} (${c.strategy}, ${((c.confidence ?? 0) * 100).toFixed(0)}%)`);
|
||||
}
|
||||
|
||||
if (r.register) lines.push(`\nRegistered: ${r.register.count ?? 0}`);
|
||||
|
||||
const fw = r.explore?.framework ?? {};
|
||||
const fwNames = Object.entries(fw).filter(([, v]) => v).map(([k]) => k);
|
||||
if (fwNames.length) lines.push(`Framework: ${fwNames.join(', ')}`);
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
Reference in new issue
Block a user