@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,315 @@
1
+ /**
2
+ * YAML pipeline executor.
3
+ * Steps: fetch, navigate, evaluate, map, filter, sort, limit, select, snapshot, click, type, wait, press, intercept.
4
+ */
5
+ import chalk from 'chalk';
6
+ export async function executePipeline(page, pipeline, ctx = {}) {
7
+ const args = ctx.args ?? {};
8
+ const debug = ctx.debug ?? false;
9
+ let data = null;
10
+ const total = pipeline.length;
11
+ for (let i = 0; i < pipeline.length; i++) {
12
+ const step = pipeline[i];
13
+ if (!step || typeof step !== 'object')
14
+ continue;
15
+ for (const [op, params] of Object.entries(step)) {
16
+ if (debug)
17
+ debugStepStart(i + 1, total, op, params);
18
+ data = await executeStep(page, op, params, data, args);
19
+ if (debug)
20
+ debugStepResult(op, data);
21
+ }
22
+ }
23
+ return data;
24
+ }
25
+ function normalizeEvaluateSource(source) {
26
+ const stripped = source.trim();
27
+ if (!stripped)
28
+ return '() => undefined';
29
+ if (stripped.startsWith('(') && stripped.endsWith(')()'))
30
+ return `() => (${stripped})`;
31
+ if (/^(async\s+)?\([^)]*\)\s*=>/.test(stripped))
32
+ return stripped;
33
+ if (/^(async\s+)?[A-Za-z_][A-Za-z0-9_]*\s*=>/.test(stripped))
34
+ return stripped;
35
+ if (stripped.startsWith('function ') || stripped.startsWith('async function '))
36
+ return stripped;
37
+ return `() => (${stripped})`;
38
+ }
39
+ function debugStepStart(stepNum, total, op, params) {
40
+ let preview = '';
41
+ if (typeof params === 'string') {
42
+ preview = params.length <= 80 ? ` → ${params}` : ` → ${params.slice(0, 77)}...`;
43
+ }
44
+ else if (params && typeof params === 'object' && !Array.isArray(params)) {
45
+ preview = ` (${Object.keys(params).join(', ')})`;
46
+ }
47
+ process.stderr.write(` ${chalk.dim(`[${stepNum}/${total}]`)} ${chalk.bold.cyan(op)}${preview}\n`);
48
+ }
49
+ function debugStepResult(op, data) {
50
+ if (data === null || data === undefined) {
51
+ process.stderr.write(` ${chalk.dim('→ (no data)')}\n`);
52
+ }
53
+ else if (Array.isArray(data)) {
54
+ process.stderr.write(` ${chalk.dim(`→ ${data.length} items`)}\n`);
55
+ }
56
+ else if (typeof data === 'object') {
57
+ const keys = Object.keys(data).slice(0, 5);
58
+ process.stderr.write(` ${chalk.dim(`→ dict (${keys.join(', ')}${Object.keys(data).length > 5 ? '...' : ''})`)}\n`);
59
+ }
60
+ else if (typeof data === 'string') {
61
+ const p = data.slice(0, 60).replace(/\n/g, '\\n');
62
+ process.stderr.write(` ${chalk.dim(`→ "${p}${data.length > 60 ? '...' : ''}"`)}\n`);
63
+ }
64
+ else {
65
+ process.stderr.write(` ${chalk.dim(`→ ${typeof data}`)}\n`);
66
+ }
67
+ }
68
+ // Single URL fetch helper
69
+ async function fetchSingle(page, url, method, queryParams, headers, args, data) {
70
+ const renderedParams = {};
71
+ for (const [k, v] of Object.entries(queryParams))
72
+ renderedParams[k] = String(render(v, { args, data }));
73
+ const renderedHeaders = {};
74
+ for (const [k, v] of Object.entries(headers))
75
+ renderedHeaders[k] = String(render(v, { args, data }));
76
+ let finalUrl = url;
77
+ if (Object.keys(renderedParams).length > 0) {
78
+ const qs = new URLSearchParams(renderedParams).toString();
79
+ finalUrl = `${finalUrl}${finalUrl.includes('?') ? '&' : '?'}${qs}`;
80
+ }
81
+ if (page === null) {
82
+ const resp = await fetch(finalUrl, { method: method.toUpperCase(), headers: renderedHeaders });
83
+ return resp.json();
84
+ }
85
+ const headersJs = JSON.stringify(renderedHeaders);
86
+ const escapedUrl = finalUrl.replace(/"/g, '\\"');
87
+ return page.evaluate(`
88
+ async () => {
89
+ const resp = await fetch("${escapedUrl}", {
90
+ method: "${method}", headers: ${headersJs}, credentials: "include"
91
+ });
92
+ return await resp.json();
93
+ }
94
+ `);
95
+ }
96
+ async function executeStep(page, op, params, data, args) {
97
+ switch (op) {
98
+ case 'navigate': {
99
+ const url = render(params, { args, data });
100
+ await page.goto(String(url));
101
+ return data;
102
+ }
103
+ case 'fetch': {
104
+ const urlOrObj = typeof params === 'string' ? params : (params?.url ?? '');
105
+ const method = params?.method ?? 'GET';
106
+ const queryParams = params?.params ?? {};
107
+ const headers = params?.headers ?? {};
108
+ const urlTemplate = String(urlOrObj);
109
+ // Per-item fetch when data is array and URL references item
110
+ if (Array.isArray(data) && urlTemplate.includes('item')) {
111
+ const results = [];
112
+ for (let i = 0; i < data.length; i++) {
113
+ const itemUrl = String(render(urlTemplate, { args, data, item: data[i], index: i }));
114
+ results.push(await fetchSingle(page, itemUrl, method, queryParams, headers, args, data));
115
+ }
116
+ return results;
117
+ }
118
+ const url = render(urlOrObj, { args, data });
119
+ return fetchSingle(page, String(url), method, queryParams, headers, args, data);
120
+ }
121
+ case 'select': {
122
+ const pathStr = String(render(params, { args, data }));
123
+ if (data && typeof data === 'object') {
124
+ let current = data;
125
+ for (const part of pathStr.split('.')) {
126
+ if (current && typeof current === 'object' && !Array.isArray(current))
127
+ current = current[part];
128
+ else if (Array.isArray(current) && /^\d+$/.test(part))
129
+ current = current[parseInt(part, 10)];
130
+ else
131
+ return null;
132
+ }
133
+ return current;
134
+ }
135
+ return data;
136
+ }
137
+ case 'evaluate': {
138
+ const js = String(render(params, { args, data }));
139
+ return page.evaluate(normalizeEvaluateSource(js));
140
+ }
141
+ case 'snapshot': {
142
+ const opts = (typeof params === 'object' && params) ? params : {};
143
+ return page.snapshot({ interactive: opts.interactive ?? false, compact: opts.compact ?? false, maxDepth: opts.max_depth, raw: opts.raw ?? false });
144
+ }
145
+ case 'click': {
146
+ await page.click(String(render(params, { args, data })).replace(/^@/, ''));
147
+ return data;
148
+ }
149
+ case 'type': {
150
+ if (typeof params === 'object' && params) {
151
+ const ref = String(render(params.ref ?? '', { args, data })).replace(/^@/, '');
152
+ const text = String(render(params.text ?? '', { args, data }));
153
+ await page.typeText(ref, text);
154
+ if (params.submit)
155
+ await page.pressKey('Enter');
156
+ }
157
+ return data;
158
+ }
159
+ case 'wait': {
160
+ if (typeof params === 'number')
161
+ await page.wait(params);
162
+ else if (typeof params === 'object' && params) {
163
+ if ('text' in params) {
164
+ const timeout = params.timeout ?? 10;
165
+ const start = Date.now();
166
+ while ((Date.now() - start) / 1000 < timeout) {
167
+ const snap = await page.snapshot({ raw: true });
168
+ if (typeof snap === 'string' && snap.includes(params.text))
169
+ break;
170
+ await page.wait(0.5);
171
+ }
172
+ }
173
+ else if ('time' in params)
174
+ await page.wait(Number(params.time));
175
+ }
176
+ else if (typeof params === 'string')
177
+ await page.wait(Number(render(params, { args, data })));
178
+ return data;
179
+ }
180
+ case 'press': {
181
+ await page.pressKey(String(render(params, { args, data })));
182
+ return data;
183
+ }
184
+ case 'map': {
185
+ if (!data || typeof data !== 'object')
186
+ return data;
187
+ let items = Array.isArray(data) ? data : [data];
188
+ if (!Array.isArray(data) && typeof data === 'object' && 'data' in data)
189
+ items = data.data;
190
+ const result = [];
191
+ for (let i = 0; i < items.length; i++) {
192
+ const item = items[i];
193
+ const row = {};
194
+ for (const [key, template] of Object.entries(params))
195
+ row[key] = render(template, { args, data, item, index: i });
196
+ result.push(row);
197
+ }
198
+ return result;
199
+ }
200
+ case 'filter': {
201
+ if (!Array.isArray(data))
202
+ return data;
203
+ return data.filter((item, i) => evalExpr(String(params), { args, item, index: i }));
204
+ }
205
+ case 'sort': {
206
+ if (!Array.isArray(data))
207
+ return data;
208
+ const key = typeof params === 'object' ? (params.by ?? '') : String(params);
209
+ const reverse = typeof params === 'object' ? params.order === 'desc' : false;
210
+ return [...data].sort((a, b) => { const va = a[key] ?? ''; const vb = b[key] ?? ''; const cmp = va < vb ? -1 : va > vb ? 1 : 0; return reverse ? -cmp : cmp; });
211
+ }
212
+ case 'limit': {
213
+ if (!Array.isArray(data))
214
+ return data;
215
+ return data.slice(0, Number(render(params, { args, data })));
216
+ }
217
+ case 'intercept': return data;
218
+ default: return data;
219
+ }
220
+ }
221
+ function render(template, ctx) {
222
+ if (typeof template !== 'string')
223
+ return template;
224
+ const fullMatch = template.match(/^\$\{\{\s*(.*?)\s*\}\}$/);
225
+ if (fullMatch)
226
+ return evalExpr(fullMatch[1].trim(), ctx);
227
+ return template.replace(/\$\{\{\s*(.*?)\s*\}\}/g, (_m, expr) => String(evalExpr(expr.trim(), ctx)));
228
+ }
229
+ function evalExpr(expr, ctx) {
230
+ const args = ctx.args ?? {};
231
+ const item = ctx.item ?? {};
232
+ const data = ctx.data;
233
+ const index = ctx.index ?? 0;
234
+ // Default filter: args.limit | default(20)
235
+ if (expr.includes('|') && expr.includes('default(')) {
236
+ const [mainExpr, rest] = expr.split('|', 2);
237
+ const defaultMatch = rest.match(/default\((.+?)\)/);
238
+ const defaultVal = defaultMatch ? defaultMatch[1] : null;
239
+ const result = resolvePath(mainExpr.trim(), { args, item, data, index });
240
+ if (result === null || result === undefined) {
241
+ if (defaultVal !== null) {
242
+ const intVal = parseInt(defaultVal, 10);
243
+ if (!isNaN(intVal) && String(intVal) === defaultVal.trim())
244
+ return intVal;
245
+ return defaultVal.replace(/^['"]|['"]$/g, '');
246
+ }
247
+ }
248
+ return result;
249
+ }
250
+ // Arithmetic: index + 1
251
+ const arithMatch = expr.match(/^([\w][\w.]*)\s*([+\-*/])\s*(\d+)$/);
252
+ if (arithMatch) {
253
+ const [, varName, op, numStr] = arithMatch;
254
+ const val = resolvePath(varName, { args, item, data, index });
255
+ if (val !== null && val !== undefined) {
256
+ const numVal = Number(val);
257
+ const num = Number(numStr);
258
+ if (!isNaN(numVal)) {
259
+ switch (op) {
260
+ case '+': return numVal + num;
261
+ case '-': return numVal - num;
262
+ case '*': return numVal * num;
263
+ case '/': return num !== 0 ? numVal / num : 0;
264
+ }
265
+ }
266
+ }
267
+ }
268
+ // JS-like fallback expression: item.tweetCount || 'N/A'
269
+ const orMatch = expr.match(/^(.+?)\s*\|\|\s*(.+)$/);
270
+ if (orMatch) {
271
+ const left = evalExpr(orMatch[1].trim(), ctx);
272
+ if (left)
273
+ return left;
274
+ const right = orMatch[2].trim();
275
+ return right.replace(/^['"]|['"]$/g, '');
276
+ }
277
+ return resolvePath(expr, { args, item, data, index });
278
+ }
279
+ function resolvePath(pathStr, ctx) {
280
+ const args = ctx.args ?? {};
281
+ const item = ctx.item ?? {};
282
+ const data = ctx.data;
283
+ const index = ctx.index ?? 0;
284
+ const parts = pathStr.split('.');
285
+ const rootName = parts[0];
286
+ let obj;
287
+ let rest;
288
+ if (rootName === 'args') {
289
+ obj = args;
290
+ rest = parts.slice(1);
291
+ }
292
+ else if (rootName === 'item') {
293
+ obj = item;
294
+ rest = parts.slice(1);
295
+ }
296
+ else if (rootName === 'data') {
297
+ obj = data;
298
+ rest = parts.slice(1);
299
+ }
300
+ else if (rootName === 'index')
301
+ return index;
302
+ else {
303
+ obj = item;
304
+ rest = parts;
305
+ }
306
+ for (const part of rest) {
307
+ if (obj && typeof obj === 'object' && !Array.isArray(obj))
308
+ obj = obj[part];
309
+ else if (Array.isArray(obj) && /^\d+$/.test(part))
310
+ obj = obj[parseInt(part, 10)];
311
+ else
312
+ return null;
313
+ }
314
+ return obj;
315
+ }
@@ -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,50 @@
1
+ /**
2
+ * Core registry: Strategy enum, Arg/CliCommand interfaces, cli() registration.
3
+ */
4
+ export declare enum Strategy {
5
+ PUBLIC = "public",
6
+ COOKIE = "cookie",
7
+ HEADER = "header",
8
+ INTERCEPT = "intercept",
9
+ UI = "ui"
10
+ }
11
+ export interface Arg {
12
+ name: string;
13
+ type?: string;
14
+ default?: any;
15
+ required?: boolean;
16
+ help?: string;
17
+ choices?: string[];
18
+ }
19
+ export interface CliCommand {
20
+ site: string;
21
+ name: string;
22
+ description: string;
23
+ domain?: string;
24
+ strategy?: Strategy;
25
+ browser?: boolean;
26
+ args: Arg[];
27
+ columns?: string[];
28
+ func?: (page: any, kwargs: Record<string, any>, debug?: boolean) => Promise<any>;
29
+ pipeline?: any[];
30
+ timeoutSeconds?: number;
31
+ source?: string;
32
+ }
33
+ export interface CliOptions {
34
+ site: string;
35
+ name: string;
36
+ description?: string;
37
+ domain?: string;
38
+ strategy?: Strategy;
39
+ browser?: boolean;
40
+ args?: Arg[];
41
+ columns?: string[];
42
+ func?: (page: any, kwargs: Record<string, any>, debug?: boolean) => Promise<any>;
43
+ pipeline?: any[];
44
+ timeoutSeconds?: number;
45
+ }
46
+ export declare function cli(opts: CliOptions): CliCommand;
47
+ export declare function getRegistry(): Map<string, CliCommand>;
48
+ export declare function fullName(cmd: CliCommand): string;
49
+ export declare function strategyLabel(cmd: CliCommand): string;
50
+ export declare function registerCommand(cmd: CliCommand): void;
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Core registry: Strategy enum, Arg/CliCommand interfaces, cli() registration.
3
+ */
4
+ export var Strategy;
5
+ (function (Strategy) {
6
+ Strategy["PUBLIC"] = "public";
7
+ Strategy["COOKIE"] = "cookie";
8
+ Strategy["HEADER"] = "header";
9
+ Strategy["INTERCEPT"] = "intercept";
10
+ Strategy["UI"] = "ui";
11
+ })(Strategy || (Strategy = {}));
12
+ const _registry = new Map();
13
+ export function cli(opts) {
14
+ const cmd = {
15
+ site: opts.site,
16
+ name: opts.name,
17
+ description: opts.description ?? '',
18
+ domain: opts.domain,
19
+ strategy: opts.strategy ?? (opts.browser === false ? Strategy.PUBLIC : Strategy.COOKIE),
20
+ browser: opts.browser ?? (opts.strategy === Strategy.PUBLIC ? false : true),
21
+ args: opts.args ?? [],
22
+ columns: opts.columns,
23
+ func: opts.func,
24
+ pipeline: opts.pipeline,
25
+ timeoutSeconds: opts.timeoutSeconds,
26
+ };
27
+ const key = fullName(cmd);
28
+ _registry.set(key, cmd);
29
+ return cmd;
30
+ }
31
+ export function getRegistry() {
32
+ return _registry;
33
+ }
34
+ export function fullName(cmd) {
35
+ return `${cmd.site}/${cmd.name}`;
36
+ }
37
+ export function strategyLabel(cmd) {
38
+ return cmd.strategy ?? 'public';
39
+ }
40
+ export function registerCommand(cmd) {
41
+ _registry.set(fullName(cmd), cmd);
42
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Runtime utilities: timeouts and browser session management.
3
+ */
4
+ export declare const DEFAULT_BROWSER_CONNECT_TIMEOUT: number;
5
+ export declare const DEFAULT_BROWSER_COMMAND_TIMEOUT: number;
6
+ export declare const DEFAULT_BROWSER_EXPLORE_TIMEOUT: number;
7
+ export declare const DEFAULT_BROWSER_SMOKE_TIMEOUT: number;
8
+ export declare function runWithTimeout<T>(promise: Promise<T>, opts: {
9
+ timeout: number;
10
+ label?: string;
11
+ }): Promise<T>;
12
+ export declare function browserSession<T>(BrowserFactory: new () => any, fn: (page: any) => Promise<T>): Promise<T>;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Runtime utilities: timeouts and browser session management.
3
+ */
4
+ export const DEFAULT_BROWSER_CONNECT_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_CONNECT_TIMEOUT ?? '30', 10);
5
+ export const DEFAULT_BROWSER_COMMAND_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_COMMAND_TIMEOUT ?? '45', 10);
6
+ export const DEFAULT_BROWSER_EXPLORE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_EXPLORE_TIMEOUT ?? '120', 10);
7
+ export const DEFAULT_BROWSER_SMOKE_TIMEOUT = parseInt(process.env.OPENCLI_BROWSER_SMOKE_TIMEOUT ?? '60', 10);
8
+ export async function runWithTimeout(promise, opts) {
9
+ return new Promise((resolve, reject) => {
10
+ const timer = setTimeout(() => {
11
+ reject(new Error(`${opts.label ?? 'Operation'} timed out after ${opts.timeout}s`));
12
+ }, opts.timeout * 1000);
13
+ promise
14
+ .then((result) => { clearTimeout(timer); resolve(result); })
15
+ .catch((err) => { clearTimeout(timer); reject(err); });
16
+ });
17
+ }
18
+ export async function browserSession(BrowserFactory, fn) {
19
+ const mcp = new BrowserFactory();
20
+ try {
21
+ const page = await mcp.connect({ timeout: DEFAULT_BROWSER_CONNECT_TIMEOUT });
22
+ return await fn(page);
23
+ }
24
+ finally {
25
+ await mcp.close().catch(() => { });
26
+ }
27
+ }
@@ -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 }; }
@@ -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 }; }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Aria snapshot formatter: parses Playwright MCP snapshot text into clean format.
3
+ */
4
+ export interface FormatOptions {
5
+ interactive?: boolean;
6
+ compact?: boolean;
7
+ maxDepth?: number;
8
+ }
9
+ export declare function formatSnapshot(raw: string, opts?: FormatOptions): string;
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Aria snapshot formatter: parses Playwright MCP snapshot text into clean format.
3
+ */
4
+ export function formatSnapshot(raw, opts = {}) {
5
+ if (!raw || typeof raw !== 'string')
6
+ return '';
7
+ const lines = raw.split('\n');
8
+ const result = [];
9
+ let refCounter = 0;
10
+ for (const line of lines) {
11
+ if (!line.trim())
12
+ continue;
13
+ const indent = line.length - line.trimStart().length;
14
+ const depth = Math.floor(indent / 2);
15
+ if (opts.maxDepth && depth > opts.maxDepth)
16
+ continue;
17
+ let content = line.trimStart();
18
+ // Skip non-interactive elements in interactive mode
19
+ if (opts.interactive) {
20
+ const interactiveRoles = ['button', 'link', 'textbox', 'checkbox', 'radio', 'combobox', 'tab', 'menuitem', 'option'];
21
+ const role = content.split(/[\s[]/)[0]?.toLowerCase() ?? '';
22
+ if (!interactiveRoles.some(r => role.includes(r)) && depth > 1)
23
+ continue;
24
+ }
25
+ // Compact: strip verbose role descriptions
26
+ if (opts.compact) {
27
+ content = content
28
+ .replace(/\s*\[.*?\]\s*/g, ' ')
29
+ .replace(/\s+/g, ' ')
30
+ .trim();
31
+ }
32
+ // Assign refs to interactive elements
33
+ const interactivePattern = /^(button|link|textbox|checkbox|radio|combobox|tab|menuitem|option)\b/i;
34
+ if (interactivePattern.test(content)) {
35
+ refCounter++;
36
+ content = `[@${refCounter}] ${content}`;
37
+ }
38
+ result.push(' '.repeat(depth) + content);
39
+ }
40
+ return result.join('\n');
41
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Synthesize: turn explore capabilities into ready-to-use CLI definitions.
3
+ *
4
+ * Takes the structured capabilities from Deep Explore and generates
5
+ * YAML pipeline files that can be directly registered as CLI commands.
6
+ *
7
+ * This is the bridge between discovery (explore) and usability (CLI).
8
+ */
9
+ export declare function synthesizeFromExplore(target: string, opts?: any): any;
10
+ export declare function renderSynthesizeSummary(r: any): string;