@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.
@@ -0,0 +1,225 @@
1
+ import { execSync } from 'child_process';
2
+ import { existsSync, readdirSync } from 'fs';
3
+ import { join } from 'path';
4
+
5
+ // src/shell-check/index.ts
6
+ var API_VERSION = "^0.1.10";
7
+ function runShellCheck(files, severity, cwd) {
8
+ if (!existsSync("shellcheck")) {
9
+ try {
10
+ execSync("shellcheck --version", { encoding: "utf-8", stdio: "ignore" });
11
+ } catch {
12
+ throw new Error("shellcheck is not installed. Install via: apt install shellcheck / brew install shellcheck");
13
+ }
14
+ }
15
+ const levelMap = {
16
+ error: "error",
17
+ warning: "warning",
18
+ info: "info",
19
+ style: "style"
20
+ };
21
+ const args = [
22
+ "-f",
23
+ "json",
24
+ "-S",
25
+ levelMap[severity] ?? "warning",
26
+ ...files
27
+ ];
28
+ let raw;
29
+ try {
30
+ raw = execSync(`shellcheck ${args.join(" ")}`, {
31
+ encoding: "utf-8",
32
+ cwd,
33
+ stdio: ["pipe", "pipe", "pipe"],
34
+ timeout: 6e4
35
+ });
36
+ } catch (err) {
37
+ const e = err;
38
+ if (e.stderr && !e.stderr.includes("shellcheck")) {
39
+ raw = e.stderr;
40
+ } else {
41
+ return [];
42
+ }
43
+ }
44
+ if (!raw.trim()) return [];
45
+ try {
46
+ const parsed = JSON.parse(raw);
47
+ return parsed.map((item) => ({
48
+ file: item.file,
49
+ line: item.line,
50
+ column: item.column,
51
+ level: item.level,
52
+ code: item.code,
53
+ message: item.message
54
+ }));
55
+ } catch {
56
+ return [];
57
+ }
58
+ }
59
+ function findShellFiles(dir, pattern) {
60
+ const results = [];
61
+ try {
62
+ const entries = readdirSync(dir, { withFileTypes: true });
63
+ for (const entry of entries) {
64
+ const full = join(dir, entry.name);
65
+ if (entry.isDirectory() && entry.name !== "node_modules" && entry.name !== ".git") {
66
+ results.push(...findShellFiles(full, pattern));
67
+ } else if (entry.isFile() && (entry.name.endsWith(".sh") || entry.name === "Dockerfile")) {
68
+ if (!pattern || entry.name.includes(pattern)) {
69
+ results.push(full);
70
+ }
71
+ }
72
+ }
73
+ } catch {
74
+ }
75
+ return results;
76
+ }
77
+ var plugin = {
78
+ name: "shell-check",
79
+ version: "0.1.0",
80
+ description: "Runs shellcheck analysis on bash/shell scripts and surfaces issues with severity levels",
81
+ apiVersion: API_VERSION,
82
+ capabilities: { tools: true, pipelines: ["toolCall"] },
83
+ defaultConfig: {
84
+ severity: "warning",
85
+ severityThreshold: "warning",
86
+ ignoredCodes: [],
87
+ autoScanOnBash: false
88
+ },
89
+ configSchema: {
90
+ type: "object",
91
+ properties: {
92
+ severity: { type: "string", enum: ["error", "warning", "info", "style"], default: "warning" },
93
+ severityThreshold: { type: "string", enum: ["error", "warning", "info", "style"], default: "warning" },
94
+ ignoredCodes: { type: "array", items: { type: "string" }, default: [] },
95
+ autoScanOnBash: { type: "boolean", default: false }
96
+ }
97
+ },
98
+ setup(api) {
99
+ api.tools.register({
100
+ name: "shellcheck",
101
+ description: "Run shellcheck analysis on shell script files. Returns issues with file, line, column, severity, code, and message.",
102
+ inputSchema: {
103
+ type: "object",
104
+ properties: {
105
+ files: {
106
+ type: "array",
107
+ items: { type: "string" },
108
+ description: "Shell script files to check"
109
+ },
110
+ severity: {
111
+ type: "string",
112
+ enum: ["error", "warning", "info", "style"],
113
+ default: "warning",
114
+ description: "Minimum severity level to report"
115
+ },
116
+ fix: {
117
+ type: "boolean",
118
+ default: false,
119
+ description: "Apply safe automatic fixes where possible"
120
+ }
121
+ },
122
+ required: ["files"]
123
+ },
124
+ permission: "auto",
125
+ mutating: false,
126
+ async execute(input) {
127
+ const files = input["files"];
128
+ const severity = input["severity"] ?? "warning";
129
+ let issues;
130
+ try {
131
+ issues = runShellCheck(files, severity);
132
+ } catch (err) {
133
+ const msg = err instanceof Error ? err.message : String(err);
134
+ return { ok: false, error: msg, issues: [] };
135
+ }
136
+ const byFile = {};
137
+ for (const issue of issues) {
138
+ (byFile[issue.file] ??= []).push(issue);
139
+ }
140
+ const errorCount = issues.filter((i) => i.level === "error").length;
141
+ const warningCount = issues.filter((i) => i.level === "warning").length;
142
+ const infoCount = issues.filter((i) => i.level === "info").length;
143
+ const styleCount = issues.filter((i) => i.level === "style").length;
144
+ api.metrics.counter("issues_found", issues.length, { severity });
145
+ api.metrics.histogram("issues_per_file", issues.length / Math.max(files.length, 1));
146
+ return {
147
+ ok: true,
148
+ filesScanned: files.length,
149
+ issues,
150
+ summary: {
151
+ total: issues.length,
152
+ errors: errorCount,
153
+ warnings: warningCount,
154
+ info: infoCount,
155
+ style: styleCount
156
+ },
157
+ byFile,
158
+ recommendation: errorCount > 0 ? "Fix errors before deploying." : warningCount > 0 ? "Review and fix warnings for better script quality." : "No issues found."
159
+ };
160
+ }
161
+ });
162
+ api.tools.register({
163
+ name: "shellcheck_scan",
164
+ description: "Recursively scan a directory for shell scripts and run shellcheck on all found files.",
165
+ inputSchema: {
166
+ type: "object",
167
+ properties: {
168
+ directory: {
169
+ type: "string",
170
+ default: ".",
171
+ description: "Directory to scan"
172
+ },
173
+ pattern: {
174
+ type: "string",
175
+ default: "",
176
+ description: "Filename pattern to match (default: all .sh files)"
177
+ },
178
+ severity: {
179
+ type: "string",
180
+ enum: ["error", "warning", "info", "style"],
181
+ default: "warning"
182
+ }
183
+ }
184
+ },
185
+ permission: "auto",
186
+ mutating: false,
187
+ async execute(input) {
188
+ const dir = input["directory"] ?? ".";
189
+ const pattern = input["pattern"] ?? "";
190
+ const severity = input["severity"] ?? "warning";
191
+ const files = findShellFiles(dir, pattern);
192
+ if (files.length === 0) {
193
+ return { ok: true, filesScanned: 0, issues: [], summary: { total: 0 } };
194
+ }
195
+ let issues;
196
+ try {
197
+ issues = runShellCheck(files, severity);
198
+ } catch (err) {
199
+ const msg = err instanceof Error ? err.message : String(err);
200
+ return { ok: false, error: msg, issues: [], filesScanned: 0 };
201
+ }
202
+ const byFile = {};
203
+ for (const issue of issues) {
204
+ (byFile[issue.file] ??= []).push(issue);
205
+ }
206
+ return {
207
+ ok: true,
208
+ filesScanned: files.length,
209
+ filesWithIssues: Object.keys(byFile).length,
210
+ issues,
211
+ summary: {
212
+ total: issues.length,
213
+ errors: issues.filter((i) => i.level === "error").length,
214
+ warnings: issues.filter((i) => i.level === "warning").length
215
+ },
216
+ byFile
217
+ };
218
+ }
219
+ });
220
+ api.log.info("shell-check plugin loaded", { version: "0.1.0" });
221
+ }
222
+ };
223
+ var shell_check_default = plugin;
224
+
225
+ export { shell_check_default as default };
@@ -0,0 +1,15 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * template-engine plugin — File template expansion with variable substitution.
5
+ *
6
+ * Tools registered:
7
+ * - template_expand: Expand a template string with variables
8
+ * - template_render: Read a template file and expand it
9
+ * - template_create: Save a named template to the plugin store
10
+ * - template_list: List all saved templates
11
+ */
12
+
13
+ declare const plugin: Plugin;
14
+
15
+ export { plugin as default };
@@ -0,0 +1,267 @@
1
+ // src/template-engine/index.ts
2
+ var API_VERSION = "^0.1.10";
3
+ function expandTemplate(template, variables) {
4
+ let result = template;
5
+ result = result.replace(/\{\{(\w+)\}\}/g, (match, key) => {
6
+ const value = variables[key];
7
+ if (value !== void 0) return value;
8
+ return match;
9
+ });
10
+ return result;
11
+ }
12
+ function expandConditionals(template, variables) {
13
+ return template.replace(
14
+ /\{\{#if\s+(\w+)\}\}([\s\S]*?)\{\{\/if\}\}/g,
15
+ (_, key, content) => {
16
+ const val = variables[key];
17
+ return val !== void 0 && val !== "" && val !== "false" && val !== "0" ? content : "";
18
+ }
19
+ );
20
+ }
21
+ function expandLoops(template, variables) {
22
+ return template.replace(
23
+ /\{\{#each\s+(\w+)\}\}([\s\S]*?)\{\{\/each\}\}/g,
24
+ (_, key, content) => {
25
+ const val = variables[key];
26
+ if (!val) return "";
27
+ if (typeof val === "string" && val.includes(",")) {
28
+ const items = val.split(",").map((s) => s.trim());
29
+ return items.map((item) => expandTemplate(content, { ...variables, [key]: item })).join("\n");
30
+ }
31
+ return expandTemplate(content, variables);
32
+ }
33
+ );
34
+ }
35
+ function renderTemplate(template, variables) {
36
+ let result = template;
37
+ result = expandConditionals(result, variables);
38
+ result = expandLoops(result, variables);
39
+ result = expandTemplate(result, variables);
40
+ {
41
+ result = result.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
42
+ }
43
+ return result;
44
+ }
45
+ function renderTemplateRaw(template, variables) {
46
+ let result = template;
47
+ result = expandConditionals(result, variables);
48
+ result = expandLoops(result, variables);
49
+ result = expandTemplate(result, variables);
50
+ return result;
51
+ }
52
+ var plugin = {
53
+ name: "template-engine",
54
+ version: "0.1.0",
55
+ description: "Expands file templates with variable substitution, conditionals, and loops",
56
+ apiVersion: API_VERSION,
57
+ capabilities: { tools: true },
58
+ defaultConfig: {
59
+ autoEscapeHtml: true,
60
+ templateDir: "./templates",
61
+ strictVariables: false
62
+ },
63
+ configSchema: {
64
+ type: "object",
65
+ properties: {
66
+ autoEscapeHtml: { type: "boolean", default: true },
67
+ templateDir: { type: "string", default: "./templates" },
68
+ strictVariables: { type: "boolean", default: false }
69
+ }
70
+ },
71
+ setup(api) {
72
+ const templates = /* @__PURE__ */ new Map();
73
+ api.tools.register({
74
+ name: "template_expand",
75
+ description: "Expand a template string with variable substitution. Supports {{variable}}, {{#if var}}...{{/if}} conditionals, and {{#each items}}...{{/each}} loops.",
76
+ inputSchema: {
77
+ type: "object",
78
+ properties: {
79
+ template: { type: "string", description: "Template string with {{variable}} placeholders" },
80
+ variables: {
81
+ type: "object",
82
+ description: "Variables to substitute into the template",
83
+ additionalProperties: { type: "string" }
84
+ },
85
+ outputPath: { type: "string", description: "Optional path to write the expanded result" },
86
+ raw: { type: "boolean", default: false, description: "Disable HTML auto-escaping" }
87
+ },
88
+ required: ["template", "variables"]
89
+ },
90
+ permission: "auto",
91
+ mutating: true,
92
+ async execute(input) {
93
+ const template = input["template"];
94
+ const variables = input["variables"];
95
+ const outputPath = input["outputPath"];
96
+ const raw = input["raw"] ?? false;
97
+ if (!template || typeof template !== "string") {
98
+ return { ok: false, error: "template is required and must be a string" };
99
+ }
100
+ if (!variables || typeof variables !== "object") {
101
+ return { ok: false, error: "variables is required and must be an object" };
102
+ }
103
+ let result;
104
+ try {
105
+ result = raw ? renderTemplateRaw(template, variables) : renderTemplate(template, variables);
106
+ } catch (err) {
107
+ return { ok: false, error: String(err) };
108
+ }
109
+ if (outputPath) {
110
+ const { writeFileSync } = await import('fs');
111
+ writeFileSync(outputPath, result, "utf-8");
112
+ return {
113
+ ok: true,
114
+ outputPath,
115
+ contentLength: result.length,
116
+ message: `Wrote ${result.length} characters to ${outputPath}`
117
+ };
118
+ }
119
+ return {
120
+ ok: true,
121
+ result,
122
+ contentLength: result.length,
123
+ variableCount: Object.keys(variables).length
124
+ };
125
+ }
126
+ });
127
+ api.tools.register({
128
+ name: "template_render",
129
+ description: "Read a template file from disk and expand it with the given variables.",
130
+ inputSchema: {
131
+ type: "object",
132
+ properties: {
133
+ templatePath: { type: "string", description: "Path to the template file" },
134
+ variables: {
135
+ type: "object",
136
+ description: "Variables to substitute",
137
+ additionalProperties: { type: "string" }
138
+ },
139
+ outputPath: { type: "string", description: "Optional path to write the rendered result" },
140
+ raw: { type: "boolean", default: false }
141
+ },
142
+ required: ["templatePath", "variables"]
143
+ },
144
+ permission: "auto",
145
+ mutating: true,
146
+ async execute(input) {
147
+ const templatePath = input["templatePath"];
148
+ const variables = input["variables"];
149
+ const outputPath = input["outputPath"];
150
+ const raw = input["raw"] ?? false;
151
+ if (!templatePath || typeof templatePath !== "string") {
152
+ return { ok: false, error: "templatePath is required and must be a string" };
153
+ }
154
+ if (!variables || typeof variables !== "object") {
155
+ return { ok: false, error: "variables is required and must be an object" };
156
+ }
157
+ let content;
158
+ try {
159
+ const { readFileSync } = await import('fs');
160
+ content = readFileSync(templatePath, "utf-8");
161
+ } catch (err) {
162
+ return { ok: false, error: `Could not read template file: ${err}` };
163
+ }
164
+ let result;
165
+ try {
166
+ result = raw ? renderTemplateRaw(content, variables) : renderTemplate(content, variables);
167
+ } catch (err) {
168
+ return { ok: false, error: `Template rendering failed: ${err}` };
169
+ }
170
+ if (outputPath) {
171
+ const { writeFileSync } = await import('fs');
172
+ writeFileSync(outputPath, result, "utf-8");
173
+ return {
174
+ ok: true,
175
+ templatePath,
176
+ outputPath,
177
+ message: `Rendered and wrote ${result.length} chars to ${outputPath}`
178
+ };
179
+ }
180
+ return {
181
+ ok: true,
182
+ templatePath,
183
+ result,
184
+ contentLength: result.length
185
+ };
186
+ }
187
+ });
188
+ api.tools.register({
189
+ name: "template_create",
190
+ description: "Save a named template to the plugin's template store for later use.",
191
+ inputSchema: {
192
+ type: "object",
193
+ properties: {
194
+ name: { type: "string", description: "Unique name for this template" },
195
+ content: { type: "string", description: "Template content with {{variable}} placeholders" },
196
+ description: { type: "string", description: "Optional description of what this template is for" }
197
+ },
198
+ required: ["name", "content"]
199
+ },
200
+ permission: "auto",
201
+ mutating: false,
202
+ async execute(input) {
203
+ const name = input["name"];
204
+ const content = input["content"];
205
+ const description = input["description"];
206
+ if (!name || typeof name !== "string" || name.trim() === "") {
207
+ return { ok: false, error: "name is required and must be a non-empty string" };
208
+ }
209
+ if (!content || typeof content !== "string") {
210
+ return { ok: false, error: "content is required and must be a string" };
211
+ }
212
+ const now = (/* @__PURE__ */ new Date()).toISOString();
213
+ const existing = templates.get(name);
214
+ const tmpl = {
215
+ name,
216
+ content,
217
+ description,
218
+ createdAt: existing?.createdAt ?? now,
219
+ updatedAt: now
220
+ };
221
+ templates.set(name, tmpl);
222
+ api.metrics.gauge("template_count", templates.size);
223
+ return {
224
+ ok: true,
225
+ name,
226
+ message: existing ? `Updated template '${name}'.` : `Created template '${name}'.`,
227
+ createdAt: tmpl.createdAt
228
+ };
229
+ }
230
+ });
231
+ api.tools.register({
232
+ name: "template_list",
233
+ description: "List all templates saved in the plugin's template store.",
234
+ inputSchema: { type: "object", properties: {} },
235
+ permission: "auto",
236
+ mutating: false,
237
+ async execute() {
238
+ const list = Array.from(templates.values()).map((t) => ({
239
+ name: t.name,
240
+ description: t.description,
241
+ contentLength: t.content.length,
242
+ createdAt: t.createdAt,
243
+ updatedAt: t.updatedAt
244
+ }));
245
+ return {
246
+ ok: true,
247
+ count: list.length,
248
+ templates: list
249
+ };
250
+ }
251
+ });
252
+ api.registerSystemPromptContributor(async () => [
253
+ {
254
+ type: "text",
255
+ text: `Template engine available:
256
+ - template_expand: expand a template string with {{variable}}, {{#if}} conditionals, {{#each}} loops
257
+ - template_render: render a template file with variables
258
+ - template_create: save a named template
259
+ - template_list: list saved templates`
260
+ }
261
+ ]);
262
+ api.log.info("template-engine plugin loaded", { version: "0.1.0" });
263
+ }
264
+ };
265
+ var template_engine_default = plugin;
266
+
267
+ export { template_engine_default as default };
@@ -0,0 +1,13 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * web-search plugin — Cached web search with deduplication and ranking.
5
+ *
6
+ * Tools registered:
7
+ * - web_search: Search the web with caching and deduplication
8
+ * - web_fetch: Fetch a URL and return content as markdown
9
+ */
10
+
11
+ declare const plugin: Plugin;
12
+
13
+ export { plugin as default };