Repository navigation
Expand file tree
/
Copy pathcc-webView.ts
More file actions
114 lines (109 loc) · 4.07 KB
/
Copy pathcc-webView.ts
File metadata and controls
114 lines (109 loc) · 4.07 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
import { tool } from "@opencode-ai/plugin"
import { spawn } from "node:child_process"
import { readFile } from "node:fs/promises"
import { existsSync } from "node:fs"
export default tool({
description:
"Fetch a URL or local HTML file, extract structured content into blocks, " +
"and download images. Writes <out-dir>/article.json and <out-dir>/images/*. " +
"Returns a summary with block count, image stats, and login-wall detection. " +
"Use this BEFORE the ccParseWebArticle skill to translate the page into bilingual Markdown.",
args: {
input: tool.schema
.string()
.describe("URL (https://...) or path to local HTML file"),
out_dir: tool.schema
.string()
.describe("Output directory (will be created if missing)"),
images_dir: tool.schema
.string()
.describe("Subdirectory for images. Default: images"),
json_out: tool.schema
.string()
.describe("Path for article.json. Default: <out-dir>/article.json"),
timeout: tool.schema
.number()
.describe("Fetch timeout in seconds. Default: 30"),
max_image_size: tool.schema
.number()
.describe("Skip images larger than this (bytes). Default: 10485760 (10MB)"),
fail_on_image_error: tool.schema
.boolean()
.describe("Exit with code 3 on image download failure"),
cookies: tool.schema
.string()
.describe(
'HTTP cookies in "key=value; key2=value2" format. ' +
"Copy from browser DevTools → Application → Cookies.",
),
},
async execute(args, _ctx) {
// Build cc-webView CLI argv from typed args
const argv: string[] = [
"extract",
args.input,
"--out-dir",
args.out_dir,
]
if (args.images_dir !== undefined)
argv.push("--images-dir", args.images_dir)
if (args.json_out !== undefined) argv.push("--json-out", args.json_out)
if (args.timeout !== undefined) argv.push("--timeout", String(args.timeout))
if (args.max_image_size !== undefined)
argv.push("--max-image-size", String(args.max_image_size))
if (args.fail_on_image_error === true) argv.push("--fail-on-image-error")
if (args.cookies !== undefined) argv.push("--cookies", args.cookies)
// Run cc-webView, capture stderr (cc-webView logs progress to stderr)
const { code, stderr } = await new Promise<{
code: number | null
stderr: string
}>((resolve, reject) => {
const proc = spawn("cc-webView", argv, {
stdio: ["ignore", "pipe", "pipe"],
})
let err = ""
proc.stderr.on("data", (d: Buffer) => {
err += d.toString()
})
proc.on("error", reject)
proc.on("close", (code: number | null) => resolve({ code, stderr: err }))
})
// Exit codes: 0 ok, 2 login wall (still success — user must authenticate),
// 1 fetch error, 3 image error.
if (code !== 0 && code !== 2) {
throw new Error(
`cc-webView failed (exit ${code}).\n` +
(stderr.trim() || "no stderr output"),
)
}
// Read the resulting article.json and return a structured summary
const jsonPath = args.json_out ?? `${args.out_dir}/article.json`
if (!existsSync(jsonPath)) {
return (
`cc-webView exited with code ${code} but ${jsonPath} was not produced.\n` +
`stderr:\n${stderr.trim()}`
)
}
const article = JSON.parse(await readFile(jsonPath, "utf-8"))
const summary = {
status: code === 2 ? "login_wall" : "ok",
title: article.title ?? null,
source: article.source ?? args.input,
blocks: article.blocks?.length ?? 0,
images: {
total: article.images_summary?.total ?? 0,
downloaded: article.images_summary?.downloaded ?? 0,
failed: article.images_summary?.failed ?? 0,
dir: `${args.out_dir}/${args.images_dir ?? "images"}`,
},
login_wall: article.login_wall?.detected
? {
patterns: article.login_wall.patterns_matched,
hint: article.login_wall.hint ?? null,
}
: null,
article_json: jsonPath,
}
return JSON.stringify(summary, null, 2)
},
})