@wrongstack/plugins 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/LICENSE +21 -0
- package/dist/auto-doc.d.ts +13 -0
- package/dist/auto-doc.js +239 -0
- package/dist/cost-tracker.d.ts +14 -0
- package/dist/cost-tracker.js +209 -0
- package/dist/cron.d.ts +14 -0
- package/dist/cron.js +223 -0
- package/dist/file-watcher.d.ts +14 -0
- package/dist/file-watcher.js +192 -0
- package/dist/git-autocommit.d.ts +14 -0
- package/dist/git-autocommit.js +305 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.js +2503 -0
- package/dist/json-path.d.ts +15 -0
- package/dist/json-path.js +311 -0
- package/dist/semver-bump.d.ts +14 -0
- package/dist/semver-bump.js +347 -0
- package/dist/shell-check.d.ts +13 -0
- package/dist/shell-check.js +225 -0
- package/dist/template-engine.d.ts +15 -0
- package/dist/template-engine.js +267 -0
- package/dist/web-search.d.ts +13 -0
- package/dist/web-search.js +210 -0
- package/package.json +75 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
// src/web-search/index.ts
|
|
2
|
+
var API_VERSION = "^0.1.10";
|
|
3
|
+
async function duckduckgoSearch(query, numResults) {
|
|
4
|
+
const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}&kl=us-en`;
|
|
5
|
+
const resp = await fetch(url, {
|
|
6
|
+
headers: {
|
|
7
|
+
"User-Agent": "Mozilla/5.0 (compatible; WrongStack/1.0; +https://wrongstack.com)"
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
if (!resp.ok) throw new Error(`DuckDuckGo search failed: ${resp.status}`);
|
|
11
|
+
const html = await resp.text();
|
|
12
|
+
const results = [];
|
|
13
|
+
const resultRe = /<a class="result__a" href="([^"]+)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
14
|
+
let m;
|
|
15
|
+
while ((m = resultRe.exec(html)) !== null && results.length < numResults) {
|
|
16
|
+
const url2 = m[1];
|
|
17
|
+
if (!url2) continue;
|
|
18
|
+
const title = (m[2] ?? "").replace(/<[^>]+>/g, "").trim();
|
|
19
|
+
const snippet = (m[3] ?? "").replace(/<[^>]+>/g, "").trim();
|
|
20
|
+
results.push({
|
|
21
|
+
url: url2,
|
|
22
|
+
title,
|
|
23
|
+
snippet,
|
|
24
|
+
score: 1,
|
|
25
|
+
source: "duckduckgo",
|
|
26
|
+
cached: false
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return results;
|
|
30
|
+
}
|
|
31
|
+
async function fetchUrl(url, format) {
|
|
32
|
+
const resp = await fetch(url, {
|
|
33
|
+
headers: {
|
|
34
|
+
"User-Agent": "Mozilla/5.0 (compatible; WrongStack/1.0; +https://wrongstack.com)",
|
|
35
|
+
"Accept": format === "text" ? "text/plain" : "text/html"
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
if (!resp.ok) throw new Error(`Failed to fetch ${url}: ${resp.status} ${resp.statusText}`);
|
|
39
|
+
if (format === "text") {
|
|
40
|
+
return resp.text();
|
|
41
|
+
}
|
|
42
|
+
let html = await resp.text();
|
|
43
|
+
html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
|
|
44
|
+
html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
|
|
45
|
+
html = html.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, (_, t) => `
|
|
46
|
+
## ${t.trim()}
|
|
47
|
+
`);
|
|
48
|
+
html = html.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, t) => `${t.trim()}
|
|
49
|
+
|
|
50
|
+
`);
|
|
51
|
+
html = html.replace(/<br\s*\/?>/gi, "\n");
|
|
52
|
+
html = html.replace(/<[^>]+>/g, "");
|
|
53
|
+
html = html.replace(/&/g, "&");
|
|
54
|
+
html = html.replace(/</g, "<");
|
|
55
|
+
html = html.replace(/>/g, ">");
|
|
56
|
+
html = html.replace(/"/g, '"');
|
|
57
|
+
html = html.replace(/'/g, "'");
|
|
58
|
+
html = html.replace(/\n{3,}/g, "\n\n");
|
|
59
|
+
return html.trim().slice(0, 5e4);
|
|
60
|
+
}
|
|
61
|
+
function scoreResults(results, query) {
|
|
62
|
+
const terms = query.toLowerCase().split(/\s+/);
|
|
63
|
+
return results.map((r) => {
|
|
64
|
+
const titleLower = r.title.toLowerCase();
|
|
65
|
+
const snippetLower = r.snippet.toLowerCase();
|
|
66
|
+
let score = r.score;
|
|
67
|
+
for (const term of terms) {
|
|
68
|
+
if (titleLower.includes(term)) score += 2;
|
|
69
|
+
if (snippetLower.includes(term)) score += 1;
|
|
70
|
+
}
|
|
71
|
+
return { ...r, score };
|
|
72
|
+
}).sort((a, b) => b.score - a.score);
|
|
73
|
+
}
|
|
74
|
+
var plugin = {
|
|
75
|
+
name: "web-search",
|
|
76
|
+
version: "0.1.0",
|
|
77
|
+
description: "Cached web search with deduplication and relevance ranking",
|
|
78
|
+
apiVersion: API_VERSION,
|
|
79
|
+
capabilities: { tools: true, pipelines: ["request"] },
|
|
80
|
+
defaultConfig: {
|
|
81
|
+
cacheTtlMs: 3e5,
|
|
82
|
+
maxResults: 10,
|
|
83
|
+
userAgent: "WrongStack/1.0"
|
|
84
|
+
},
|
|
85
|
+
configSchema: {
|
|
86
|
+
type: "object",
|
|
87
|
+
properties: {
|
|
88
|
+
cacheTtlMs: { type: "number", default: 3e5 },
|
|
89
|
+
maxResults: { type: "number", default: 10 },
|
|
90
|
+
userAgent: { type: "string", default: "WrongStack/1.0" }
|
|
91
|
+
}
|
|
92
|
+
},
|
|
93
|
+
setup(api) {
|
|
94
|
+
const cache = /* @__PURE__ */ new Map();
|
|
95
|
+
const cacheTtlMs = api.config.extensions?.["web-search"]?.["cacheTtlMs"] ?? 3e5;
|
|
96
|
+
const maxResults = api.config.extensions?.["web-search"]?.["maxResults"] ?? 10;
|
|
97
|
+
api.tools.register({
|
|
98
|
+
name: "web_search",
|
|
99
|
+
description: "Search the web using DuckDuckGo with automatic caching and deduplication. Results are cached for faster subsequent queries.",
|
|
100
|
+
inputSchema: {
|
|
101
|
+
type: "object",
|
|
102
|
+
properties: {
|
|
103
|
+
query: { type: "string", description: "Search query" },
|
|
104
|
+
numResults: { type: "number", default: 10, description: "Maximum number of results" },
|
|
105
|
+
source: { type: "string", enum: ["duckduckgo"], default: "duckduckgo", description: "Search engine" },
|
|
106
|
+
skipCache: { type: "boolean", default: false, description: "Skip cache and force fresh search" }
|
|
107
|
+
},
|
|
108
|
+
required: ["query"]
|
|
109
|
+
},
|
|
110
|
+
permission: "auto",
|
|
111
|
+
mutating: false,
|
|
112
|
+
async execute(input) {
|
|
113
|
+
const query = input["query"];
|
|
114
|
+
if (!query || typeof query !== "string" || query.trim() === "") {
|
|
115
|
+
return { ok: false, error: "query is required and must be a non-empty string", results: [] };
|
|
116
|
+
}
|
|
117
|
+
const numResults = input["numResults"] ?? maxResults;
|
|
118
|
+
const skipCache = input["skipCache"] ?? false;
|
|
119
|
+
if (!skipCache) {
|
|
120
|
+
const cached = cache.get(query);
|
|
121
|
+
if (cached && Date.now() - cached.timestamp < cacheTtlMs) {
|
|
122
|
+
const results = cached.results.map((r) => ({ ...r, cached: true }));
|
|
123
|
+
api.metrics.counter("cache_hit", 1, { query: query.slice(0, 20) });
|
|
124
|
+
return {
|
|
125
|
+
ok: true,
|
|
126
|
+
query,
|
|
127
|
+
cached: true,
|
|
128
|
+
results: results.slice(0, numResults),
|
|
129
|
+
count: results.length
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
api.metrics.counter("cache_miss", 1, { query: query.slice(0, 20) });
|
|
134
|
+
const seenUrls = /* @__PURE__ */ new Set();
|
|
135
|
+
let rawResults;
|
|
136
|
+
try {
|
|
137
|
+
rawResults = await duckduckgoSearch(query, numResults * 2);
|
|
138
|
+
} catch (err) {
|
|
139
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
140
|
+
return { ok: false, error: `Search failed: ${msg}`, results: [] };
|
|
141
|
+
}
|
|
142
|
+
const deduplicated = [];
|
|
143
|
+
for (const r of rawResults) {
|
|
144
|
+
const normalized = (r.url.split("?")[0] ?? r.url).split("#")[0] ?? r.url;
|
|
145
|
+
if (!seenUrls.has(normalized) && r.url.startsWith("http")) {
|
|
146
|
+
seenUrls.add(normalized);
|
|
147
|
+
deduplicated.push(r);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
const ranked = scoreResults(deduplicated, query);
|
|
151
|
+
cache.set(query, { results: ranked, timestamp: Date.now() });
|
|
152
|
+
const now = Date.now();
|
|
153
|
+
for (const [key, entry] of cache.entries()) {
|
|
154
|
+
if (now - entry.timestamp > cacheTtlMs * 2) cache.delete(key);
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
ok: true,
|
|
158
|
+
query,
|
|
159
|
+
cached: false,
|
|
160
|
+
results: ranked.slice(0, numResults),
|
|
161
|
+
count: ranked.length
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
api.tools.register({
|
|
166
|
+
name: "web_fetch",
|
|
167
|
+
description: "Fetch a URL and return its content as markdown or plain text.",
|
|
168
|
+
inputSchema: {
|
|
169
|
+
type: "object",
|
|
170
|
+
properties: {
|
|
171
|
+
url: { type: "string", format: "uri", description: "URL to fetch" },
|
|
172
|
+
format: { type: "string", enum: ["markdown", "text"], default: "markdown" }
|
|
173
|
+
},
|
|
174
|
+
required: ["url"]
|
|
175
|
+
},
|
|
176
|
+
permission: "confirm",
|
|
177
|
+
mutating: false,
|
|
178
|
+
async execute(input) {
|
|
179
|
+
const rawUrl = input["url"];
|
|
180
|
+
if (!rawUrl || typeof rawUrl !== "string") {
|
|
181
|
+
return { ok: false, error: "url is required and must be a string" };
|
|
182
|
+
}
|
|
183
|
+
const url = rawUrl;
|
|
184
|
+
const format = input["format"] ?? "markdown";
|
|
185
|
+
if (!url.startsWith("http://") && !url.startsWith("https://")) {
|
|
186
|
+
return { ok: false, error: "URL must start with http:// or https://" };
|
|
187
|
+
}
|
|
188
|
+
let content;
|
|
189
|
+
try {
|
|
190
|
+
content = await fetchUrl(url, format);
|
|
191
|
+
} catch (err) {
|
|
192
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
193
|
+
return { ok: false, error: msg };
|
|
194
|
+
}
|
|
195
|
+
return {
|
|
196
|
+
ok: true,
|
|
197
|
+
url,
|
|
198
|
+
format,
|
|
199
|
+
contentLength: content.length,
|
|
200
|
+
content: content.slice(0, 2e4),
|
|
201
|
+
truncated: content.length > 2e4
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
api.log.info("web-search plugin loaded", { version: "0.1.0", cacheTtlMs });
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
var web_search_default = plugin;
|
|
209
|
+
|
|
210
|
+
export { web_search_default as default };
|
package/package.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@wrongstack/plugins",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Official WrongStack plugin collection — auto-doc, git-autocommit, shell-check, cost-tracker, file-watcher, web-search, json-path, cron, template-engine, semver-bump",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "ECOSTACK TECHNOLOGY OÜ",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./auto-doc": {
|
|
16
|
+
"types": "./dist/auto-doc/index.d.ts",
|
|
17
|
+
"import": "./dist/auto-doc/index.js"
|
|
18
|
+
},
|
|
19
|
+
"./git-autocommit": {
|
|
20
|
+
"types": "./dist/git-autocommit/index.d.ts",
|
|
21
|
+
"import": "./dist/git-autocommit/index.js"
|
|
22
|
+
},
|
|
23
|
+
"./shell-check": {
|
|
24
|
+
"types": "./dist/shell-check/index.d.ts",
|
|
25
|
+
"import": "./dist/shell-check/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./cost-tracker": {
|
|
28
|
+
"types": "./dist/cost-tracker/index.d.ts",
|
|
29
|
+
"import": "./dist/cost-tracker/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./file-watcher": {
|
|
32
|
+
"types": "./dist/file-watcher/index.d.ts",
|
|
33
|
+
"import": "./dist/file-watcher/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./web-search": {
|
|
36
|
+
"types": "./dist/web-search/index.d.ts",
|
|
37
|
+
"import": "./dist/web-search/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./json-path": {
|
|
40
|
+
"types": "./dist/json-path/index.d.ts",
|
|
41
|
+
"import": "./dist/json-path/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./cron": {
|
|
44
|
+
"types": "./dist/cron/index.d.ts",
|
|
45
|
+
"import": "./dist/cron/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./template-engine": {
|
|
48
|
+
"types": "./dist/template-engine/index.d.ts",
|
|
49
|
+
"import": "./dist/template-engine/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./semver-bump": {
|
|
52
|
+
"types": "./dist/semver-bump/index.d.ts",
|
|
53
|
+
"import": "./dist/semver-bump/index.js"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
"files": [
|
|
57
|
+
"dist"
|
|
58
|
+
],
|
|
59
|
+
"devDependencies": {
|
|
60
|
+
"@types/node": "^22.19.19",
|
|
61
|
+
"tsup": "^8.5.1",
|
|
62
|
+
"typescript": "^5.9.3",
|
|
63
|
+
"vitest": "^3.0.0"
|
|
64
|
+
},
|
|
65
|
+
"dependencies": {
|
|
66
|
+
"@wrongstack/core": "0.6.4"
|
|
67
|
+
},
|
|
68
|
+
"scripts": {
|
|
69
|
+
"build": "tsup",
|
|
70
|
+
"typecheck": "tsc --noEmit",
|
|
71
|
+
"test": "vitest run",
|
|
72
|
+
"test:watch": "vitest",
|
|
73
|
+
"clean": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\""
|
|
74
|
+
}
|
|
75
|
+
}
|