@syngy/create-webskill 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.
package/README.md ADDED
@@ -0,0 +1,9 @@
1
+ # @syngy/create-webskill
2
+
3
+ Create a BotWorks web skill package skeleton.
4
+
5
+ ```bash
6
+ npm create @syngy/webskill web-skills/example -- --title "Example workflow" --domain example.com
7
+ ```
8
+
9
+ Edit the generated files, then publish them from BotWorks with `browser_use.web_skill_publish`.
@@ -0,0 +1,127 @@
1
+ #!/usr/bin/env node
2
+ import { mkdir, readdir, writeFile } from 'node:fs/promises'
3
+ import path from 'node:path'
4
+
5
+ function parseArgs(argv) {
6
+ const out = { filepath: '', title: '', domain: '' }
7
+ const rest = []
8
+ for (let i = 0; i < argv.length; i += 1) {
9
+ const arg = argv[i]
10
+ if (arg === '--title') {
11
+ out.title = argv[++i] || ''
12
+ } else if (arg === '--domain') {
13
+ out.domain = argv[++i] || ''
14
+ } else if (!arg.startsWith('--')) {
15
+ rest.push(arg)
16
+ }
17
+ }
18
+ out.filepath = rest[0] || ''
19
+ return out
20
+ }
21
+
22
+ function slugify(input) {
23
+ return input
24
+ .trim()
25
+ .toLowerCase()
26
+ .replace(/[^a-z0-9]+/g, '-')
27
+ .replace(/^-+|-+$/g, '')
28
+ }
29
+
30
+ function filesFor({ title, domain }) {
31
+ const manifest = {
32
+ name: title,
33
+ description: `Reusable browser workflow for ${domain}.`,
34
+ domains: [domain],
35
+ pathPatterns: ['/**'],
36
+ entryPath: 'SKILL.md',
37
+ tools: []
38
+ }
39
+ return {
40
+ 'SKILL.md': `# ${title}
41
+
42
+ ## 适用范围
43
+
44
+ - Domain: \`${domain}\`
45
+ - 默认匹配路径: \`/**\`
46
+
47
+ ## 成功链路
48
+
49
+ 把已经验证通过的浏览器操作步骤写在这里。步骤应包含入口页面、关键按钮或输入框、必要等待条件、完成后的可验证页面状态。
50
+
51
+ ## 脚本
52
+
53
+ 脚本文件放在 \`scripts/\` 下。需要暴露脚本工具时,在 \`manifest.json\` 的 \`tools\` 中增加工具名、说明、脚本路径和 inputSchema。
54
+
55
+ 脚本入口固定为:
56
+
57
+ \`\`\`js
58
+ async function run(args, context) {
59
+ return { result: { title: context.title, url: context.url } };
60
+ }
61
+ \`\`\`
62
+
63
+ 可用的页面工具在 \`scripts/README.md\`。
64
+ `,
65
+ 'manifest.json': `${JSON.stringify(manifest, null, 2)}\n`,
66
+ 'scripts/README.md': `# pageTool
67
+
68
+ Web skill scripts receive \`args\` and \`context\`.
69
+
70
+ - \`context.url\`
71
+ - \`context.title\`
72
+ - \`context.store\`
73
+ - \`context.pageTool\`
74
+
75
+ Common calls:
76
+
77
+ \`\`\`js
78
+ await context.pageTool.element.click({ ref: "e1" })
79
+ await context.pageTool.element.type({ ref: "e2" }, "hello")
80
+ await context.pageTool.keyboard.press("Enter")
81
+ const text = await context.pageTool.element.text({ ref: "e3" })
82
+ const seen = await context.pageTool.visual.getViewportContent({ type: "object", properties: { title: { type: "string" } }, required: ["title"] })
83
+ const code = await context.pageTool.visual.getImageContent({ ref: "e4" }, { type: "object", properties: { value: { type: "string" } }, required: ["value"] })
84
+ \`\`\`
85
+
86
+ Method groups:
87
+
88
+ - \`page.url()\`, \`page.title()\`, \`page.viewport()\`, \`page.waitForLoadState(state)\`, \`page.waitForTimeout(ms)\`
89
+ - \`keyboard.type(text)\`, \`keyboard.press(key)\`, \`keyboard.down(key)\`, \`keyboard.up(key)\`, \`keyboard.insertText(text)\`
90
+ - \`mouse.move(x, y)\`, \`mouse.click(x, y)\`, \`mouse.dblclick(x, y)\`, \`mouse.down()\`, \`mouse.up()\`, \`mouse.wheel(deltaX, deltaY)\`
91
+ - \`element.click(target)\`, \`element.hover(target)\`, \`element.type(target, text)\`, \`element.fill(target, value)\`, \`element.press(target, key)\`
92
+ - \`element.text(target)\`, \`element.html(target)\`, \`element.box(target)\`, \`element.exists(target)\`
93
+ - \`visual.getElementContent(target, schema)\`, \`visual.getViewportContent(schema)\`, \`visual.getImageContent(target, schema)\`
94
+ `,
95
+ 'scripts/example.js': `async function run(args, context) {
96
+ return { result: { title: context.title, url: context.url } };
97
+ }
98
+ `,
99
+ '.webskill/source.json': `${JSON.stringify({ source: 'init' }, null, 2)}\n`
100
+ }
101
+ }
102
+
103
+ async function main() {
104
+ const opts = parseArgs(process.argv.slice(2))
105
+ if (!opts.title || !opts.domain) {
106
+ console.error('Usage: npm create @syngy/webskill <filepath> -- --title <title> --domain <domain>')
107
+ process.exit(2)
108
+ }
109
+ const target = path.resolve(opts.filepath || path.join('web-skills', slugify(opts.title) || 'web-skill'))
110
+ await mkdir(target, { recursive: true })
111
+ const entries = await readdir(target)
112
+ if (entries.length > 0) {
113
+ console.error(`Target directory is not empty: ${target}`)
114
+ process.exit(2)
115
+ }
116
+ for (const [relative, content] of Object.entries(filesFor(opts))) {
117
+ const full = path.join(target, relative)
118
+ await mkdir(path.dirname(full), { recursive: true })
119
+ await writeFile(full, content, 'utf8')
120
+ }
121
+ console.log(`Created web skill package at ${target}`)
122
+ }
123
+
124
+ main().catch((err) => {
125
+ console.error(err instanceof Error ? err.message : String(err))
126
+ process.exit(1)
127
+ })
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "@syngy/create-webskill",
3
+ "version": "0.1.0",
4
+ "description": "Create a BotWorks web skill package skeleton",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-webskill": "bin/create-webskill.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "README.md"
12
+ ],
13
+ "publishConfig": {
14
+ "access": "public"
15
+ }
16
+ }