@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 ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ECOSTACK TECHNOLOGY OÜ
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,13 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * auto-doc plugin — Auto-generates JSDoc/TSDoc comments for source files.
5
+ *
6
+ * Tools registered:
7
+ * - auto_doc: Generate and inject doc comments into JS/TS files
8
+ * - auto_doc_preview: Preview doc comments without writing them
9
+ */
10
+
11
+ declare const plugin: Plugin;
12
+
13
+ export { plugin as default };
@@ -0,0 +1,239 @@
1
+ // src/auto-doc/index.ts
2
+ var AUTO_DOC_API_VERSION = "^0.1.10";
3
+ function parseSource(content) {
4
+ const entities = [];
5
+ const lines = content.split("\n");
6
+ const reFunction = /^(?:export\s+)?(?:async\s+)?function\s+(\w+)\s*\((.*?)\)(?:\s*:\s*(.+?))?\s*\{/;
7
+ const reArrowFn = /^(?:export\s+)?const\s+(\w+)\s*=\s*(?:async\s+)?\((.*?)\)\s*(?::\s*(.+?))?\s*=>/;
8
+ const reClass = /^(?:export\s+)?class\s+(\w+)/;
9
+ const reType = /^(?:export\s+)?type\s+(\w+)\s*=\s*\{/;
10
+ const reInterface = /^(?:export\s+)?interface\s+(\w+)/;
11
+ for (let i = 0; i < lines.length; i++) {
12
+ const line = lines[i].trim();
13
+ let m = line.match(reFunction);
14
+ if (m) {
15
+ entities.push({
16
+ kind: "function",
17
+ name: m[1],
18
+ startLine: i + 1,
19
+ params: m[2] ? m[2].split(",").map((p) => p.trim().split(":")[0].trim()) : [],
20
+ returnType: m[3]?.trim()
21
+ });
22
+ continue;
23
+ }
24
+ m = line.match(reArrowFn);
25
+ if (m) {
26
+ entities.push({
27
+ kind: "function",
28
+ name: m[1],
29
+ startLine: i + 1,
30
+ params: m[2] ? m[2].split(",").map((p) => p.trim().split(":")[0].trim()) : [],
31
+ returnType: m[3]?.trim()
32
+ });
33
+ continue;
34
+ }
35
+ m = line.match(reClass);
36
+ if (m) {
37
+ entities.push({ kind: "class", name: m[1], startLine: i + 1 });
38
+ continue;
39
+ }
40
+ m = line.match(reType);
41
+ if (m) {
42
+ entities.push({ kind: "type", name: m[1], startLine: i + 1 });
43
+ continue;
44
+ }
45
+ m = line.match(reInterface);
46
+ if (m) {
47
+ entities.push({ kind: "interface", name: m[1], startLine: i + 1 });
48
+ continue;
49
+ }
50
+ }
51
+ return entities;
52
+ }
53
+ function generateJSDoc(entity, includeTypes) {
54
+ switch (entity.kind) {
55
+ case "function": {
56
+ const params = entity.params.map((p) => ` * @param ${p} - TODO: describe parameter`).join("\n");
57
+ const returns = entity.returnType ? `
58
+ * @returns ${includeTypes ? `{${entity.returnType}} ` : ""}TODO: describe return value` : "";
59
+ return `/**
60
+ * TODO: One-line description of ${entity.name}
61
+ ${params}${returns}
62
+ */`;
63
+ }
64
+ case "class":
65
+ return `/**
66
+ * TODO: Describe class ${entity.name}
67
+ */`;
68
+ case "type":
69
+ return `/**
70
+ * TODO: Describe type ${entity.name}
71
+ */`;
72
+ case "interface":
73
+ return `/**
74
+ * TODO: Describe interface ${entity.name}
75
+ */`;
76
+ }
77
+ }
78
+ function generateTSDoc(entity, includeTypes) {
79
+ switch (entity.kind) {
80
+ case "function": {
81
+ const params = entity.params.map((p) => ` * @param ${p} - TODO: describe parameter`).join("\n");
82
+ const returns = entity.returnType ? `
83
+ * @returns ${includeTypes ? `{${entity.returnType}} ` : ""}TODO: describe return value` : "";
84
+ return `/**
85
+ * TODO: One-line description of ${entity.name}
86
+ ${params}${returns}
87
+ */`;
88
+ }
89
+ case "class":
90
+ return `/**
91
+ * TODO: Describe class ${entity.name}
92
+ */`;
93
+ case "type":
94
+ return `/**
95
+ * TODO: Describe type ${entity.name}
96
+ */`;
97
+ case "interface":
98
+ return `/**
99
+ * TODO: Describe interface ${entity.name}
100
+ */`;
101
+ }
102
+ }
103
+ function needsDocComment(content, entity) {
104
+ const lines = content.split("\n");
105
+ const lineIdx = entity.startLine - 1;
106
+ if (lineIdx < 1) return true;
107
+ const prevLine = lines[lineIdx - 1] ?? "";
108
+ return !/^\s*\/\*\*\s*$/.test(prevLine.trim());
109
+ }
110
+ function injectDocComment(content, entity, doc) {
111
+ const lines = content.split("\n");
112
+ const idx = entity.startLine - 1;
113
+ const codeLine = lines[idx] ?? "";
114
+ const indent = codeLine.match(/^(\s*)/)?.[1] ?? "";
115
+ lines.splice(idx, 0, `${indent}${doc} ${codeLine.trim()}`);
116
+ return lines.join("\n");
117
+ }
118
+ async function runAutoDoc(input, api) {
119
+ if (!input.files || typeof input.files !== "object" || !Array.isArray(input.files)) {
120
+ return { ok: false, error: "input.files must be an array of file paths", filesProcessed: 0, changes: [] };
121
+ }
122
+ if (input.files.length === 0) {
123
+ return { ok: false, error: "input.files is empty \u2014 provide at least one file path", filesProcessed: 0, changes: [] };
124
+ }
125
+ const style = input.style ?? "tsdoc";
126
+ const includeTypes = api.config.extensions?.["auto-doc"]?.["includeTypes"] ?? false;
127
+ const results = [];
128
+ for (const file of input.files) {
129
+ try {
130
+ const { readFileSync, writeFileSync } = await import('fs');
131
+ let content;
132
+ try {
133
+ content = readFileSync(file, "utf-8");
134
+ } catch {
135
+ api.log.warn(`auto-doc: could not read file ${file}`);
136
+ continue;
137
+ }
138
+ const entities = parseSource(content);
139
+ let modified = content;
140
+ for (const entity of entities) {
141
+ if (!input.force && !needsDocComment(modified, entity)) continue;
142
+ const doc = style === "jsdoc" ? generateJSDoc(entity, includeTypes) : generateTSDoc(entity, includeTypes);
143
+ modified = injectDocComment(modified, entity, doc);
144
+ results.push({ file, entity: entity.name });
145
+ }
146
+ if (!input.dryRun && results.length > 0) {
147
+ writeFileSync(file, modified, "utf-8");
148
+ api.log.info(`auto-doc: updated ${file}`);
149
+ }
150
+ } catch (err) {
151
+ api.log.error(`auto-doc: error processing ${file}: ${err}`);
152
+ }
153
+ }
154
+ return { ok: true, filesProcessed: input.files.length, changes: results };
155
+ }
156
+ async function runAutoDocPreview(input, api) {
157
+ if (!input.files || typeof input.files !== "object" || !Array.isArray(input.files)) {
158
+ return { ok: false, error: "input.files must be an array of file paths", previews: [] };
159
+ }
160
+ if (input.files.length === 0) {
161
+ return { ok: false, error: "input.files is empty \u2014 provide at least one file path", previews: [] };
162
+ }
163
+ const style = input.style ?? "tsdoc";
164
+ const includeTypes = api.config.extensions?.["auto-doc"]?.["includeTypes"] ?? false;
165
+ const previews = [];
166
+ for (const file of input.files) {
167
+ try {
168
+ const { readFileSync } = await import('fs');
169
+ const content = readFileSync(file, "utf-8");
170
+ const entities = parseSource(content);
171
+ const generated = entities.filter((e) => !needsDocComment(content, e)).map((e) => style === "jsdoc" ? generateJSDoc(e, includeTypes) : generateTSDoc(e, includeTypes));
172
+ previews.push({ file, entities: generated });
173
+ } catch {
174
+ api.log.warn(`auto-doc-preview: could not read file ${file}`);
175
+ }
176
+ }
177
+ return { ok: true, previews };
178
+ }
179
+ var plugin = {
180
+ name: "auto-doc",
181
+ version: "0.1.0",
182
+ description: "Auto-generates JSDoc/TSDoc comments for functions, classes, types, and interfaces",
183
+ apiVersion: AUTO_DOC_API_VERSION,
184
+ capabilities: { tools: true, pipelines: ["toolCall"] },
185
+ defaultConfig: { style: "tsdoc", includeTypes: false, dryRun: false },
186
+ configSchema: {
187
+ type: "object",
188
+ properties: {
189
+ style: { type: "string", enum: ["jsdoc", "tsdoc"], default: "tsdoc" },
190
+ includeTypes: { type: "boolean", default: false },
191
+ dryRun: { type: "boolean", default: false }
192
+ }
193
+ },
194
+ setup(api) {
195
+ api.tools.register({
196
+ name: "auto_doc",
197
+ description: "Auto-generate JSDoc/TSDoc comments for functions, classes, types, and interfaces in source files",
198
+ inputSchema: {
199
+ type: "object",
200
+ properties: {
201
+ files: { type: "array", items: { type: "string" }, description: "Source files to document" },
202
+ style: { type: "string", enum: ["jsdoc", "tsdoc"], default: "tsdoc", description: "Comment style" },
203
+ force: { type: "boolean", default: false, description: "Overwrite existing docstrings" },
204
+ dryRun: { type: "boolean", default: false, description: "Preview without writing" }
205
+ },
206
+ required: ["files"]
207
+ },
208
+ permission: "auto",
209
+ mutating: true,
210
+ async execute(input) {
211
+ return runAutoDoc(input, api);
212
+ }
213
+ });
214
+ api.tools.register({
215
+ name: "auto_doc_preview",
216
+ description: "Preview what JSDoc/TSDoc comments would be generated for files, without writing",
217
+ inputSchema: {
218
+ type: "object",
219
+ properties: {
220
+ files: { type: "array", items: { type: "string" }, description: "Source files to preview" },
221
+ style: { type: "string", enum: ["jsdoc", "tsdoc"], default: "tsdoc" }
222
+ },
223
+ required: ["files"]
224
+ },
225
+ permission: "auto",
226
+ mutating: false,
227
+ async execute(input) {
228
+ return runAutoDocPreview(input, api);
229
+ }
230
+ });
231
+ api.log.info("auto-doc plugin loaded", { version: "0.1.0", capabilities: ["auto_doc", "auto_doc_preview"] });
232
+ },
233
+ teardown(api) {
234
+ api.log.info("auto-doc plugin unloaded");
235
+ }
236
+ };
237
+ var auto_doc_default = plugin;
238
+
239
+ export { auto_doc_default as default };
@@ -0,0 +1,14 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * cost-tracker plugin — Tracks LLM token usage and cost per session.
5
+ *
6
+ * Tools registered:
7
+ * - cost_summary: Show token usage breakdown by model
8
+ * - cost_reset: Reset tracking counters
9
+ * - cost_export: Export cost report as JSON or CSV
10
+ */
11
+
12
+ declare const plugin: Plugin;
13
+
14
+ export { plugin as default };
@@ -0,0 +1,209 @@
1
+ // src/cost-tracker/index.ts
2
+ var API_VERSION = "^0.1.10";
3
+ var PRICING = {
4
+ "gpt-4o": { input: 5, output: 15 },
5
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
6
+ "gpt-4-turbo": { input: 10, output: 30 },
7
+ "claude-3-5-sonnet": { input: 3, output: 15 },
8
+ "claude-3-5-haiku": { input: 0.8, output: 4 },
9
+ "claude-3-opus": { input: 15, output: 75 },
10
+ "gemini-1.5-pro": { input: 3.5, output: 10.5 },
11
+ "gemini-1.5-flash": { input: 0.075, output: 0.3 },
12
+ "default": { input: 5, output: 15 }
13
+ };
14
+ var DEFAULT_PRICING = { input: 5, output: 15 };
15
+ function estimateCost(model, promptTokens, completionTokens) {
16
+ const pricing = PRICING[model.toLowerCase()] ?? DEFAULT_PRICING;
17
+ const inputCost = promptTokens / 1e6 * pricing.input;
18
+ const outputCost = completionTokens / 1e6 * pricing.output;
19
+ return inputCost + outputCost;
20
+ }
21
+ var plugin = {
22
+ name: "cost-tracker",
23
+ version: "0.1.0",
24
+ description: "Tracks LLM token usage and estimated cost per session with per-model breakdown",
25
+ apiVersion: API_VERSION,
26
+ capabilities: { tools: true, pipelines: ["request", "response"] },
27
+ defaultConfig: {
28
+ trackPerModel: true,
29
+ trackPerUser: false,
30
+ budgetLimit: 0,
31
+ warningThreshold: 80
32
+ },
33
+ configSchema: {
34
+ type: "object",
35
+ properties: {
36
+ trackPerModel: { type: "boolean", default: true },
37
+ trackPerUser: { type: "boolean", default: false },
38
+ budgetLimit: { type: "number", default: 0, description: "Budget limit in USD (0 = no limit)" },
39
+ warningThreshold: { type: "number", default: 80, description: "Warning threshold as percentage of budget" }
40
+ }
41
+ },
42
+ setup(api) {
43
+ const sessionCost = {
44
+ requests: [],
45
+ totalPromptTokens: 0,
46
+ totalCompletionTokens: 0,
47
+ totalTokens: 0,
48
+ totalCostUsd: 0,
49
+ byModel: {}
50
+ };
51
+ api.onEvent("provider.response", (payload) => {
52
+ const usage = payload.usage;
53
+ const model = payload.ctx?.model ?? "unknown";
54
+ const promptTokens = usage.input ?? 0;
55
+ const completionTokens = usage.output ?? 0;
56
+ const totalTokens = promptTokens + completionTokens;
57
+ const costUsd = estimateCost(model, promptTokens, completionTokens);
58
+ const record = {
59
+ promptTokens,
60
+ completionTokens,
61
+ totalTokens,
62
+ model,
63
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
64
+ costUsd
65
+ };
66
+ sessionCost.requests.push(record);
67
+ sessionCost.totalPromptTokens += promptTokens;
68
+ sessionCost.totalCompletionTokens += completionTokens;
69
+ sessionCost.totalTokens += totalTokens;
70
+ sessionCost.totalCostUsd += costUsd;
71
+ const slot = sessionCost.byModel[model] ??= { tokens: 0, costUsd: 0, requests: 0 };
72
+ slot.tokens += totalTokens;
73
+ slot.costUsd += costUsd;
74
+ slot.requests += 1;
75
+ api.metrics.counter("tokens_total", totalTokens, { model });
76
+ api.metrics.histogram("cost_usd", costUsd, { model });
77
+ });
78
+ api.tools.register({
79
+ name: "cost_summary",
80
+ description: "Returns the current session's token usage breakdown by model, total cost estimate, and budget status.",
81
+ inputSchema: { type: "object", properties: {} },
82
+ permission: "auto",
83
+ mutating: false,
84
+ async execute() {
85
+ const budgetLimit = api.config.extensions?.["cost-tracker"]?.["budgetLimit"] ?? 0;
86
+ const warningThreshold = api.config.extensions?.["cost-tracker"]?.["warningThreshold"] ?? 80;
87
+ const usage = {
88
+ totalRequests: sessionCost.requests.length,
89
+ totalPromptTokens: sessionCost.totalPromptTokens,
90
+ totalCompletionTokens: sessionCost.totalCompletionTokens,
91
+ totalTokens: sessionCost.totalTokens,
92
+ totalCostUsd: Math.round(sessionCost.totalCostUsd * 1e6) / 1e6,
93
+ byModel: sessionCost.byModel,
94
+ recentRequests: sessionCost.requests.slice(-5).map((r) => ({
95
+ model: r.model,
96
+ tokens: r.totalTokens,
97
+ costUsd: r.costUsd,
98
+ ts: r.timestamp
99
+ }))
100
+ };
101
+ const budgetStatus = budgetLimit > 0 ? {
102
+ limit: budgetLimit,
103
+ spent: sessionCost.totalCostUsd,
104
+ percentUsed: Math.round(sessionCost.totalCostUsd / budgetLimit * 100),
105
+ warning: sessionCost.totalCostUsd / budgetLimit * 100 >= warningThreshold
106
+ } : null;
107
+ return {
108
+ ok: true,
109
+ usage,
110
+ budgetStatus
111
+ };
112
+ }
113
+ });
114
+ api.tools.register({
115
+ name: "cost_reset",
116
+ description: "Resets all token usage and cost counters for the current session.",
117
+ inputSchema: { type: "object", properties: {} },
118
+ permission: "auto",
119
+ mutating: false,
120
+ async execute() {
121
+ const prev = {
122
+ totalTokens: sessionCost.totalTokens,
123
+ totalCostUsd: sessionCost.totalCostUsd
124
+ };
125
+ sessionCost.requests = [];
126
+ sessionCost.totalPromptTokens = 0;
127
+ sessionCost.totalCompletionTokens = 0;
128
+ sessionCost.totalTokens = 0;
129
+ sessionCost.totalCostUsd = 0;
130
+ sessionCost.byModel = {};
131
+ return {
132
+ ok: true,
133
+ previousTotals: prev,
134
+ message: "Cost tracking counters have been reset."
135
+ };
136
+ }
137
+ });
138
+ api.tools.register({
139
+ name: "cost_export",
140
+ description: "Export the cost report as JSON or CSV.",
141
+ inputSchema: {
142
+ type: "object",
143
+ properties: {
144
+ format: { type: "string", enum: ["json", "csv"], default: "json" },
145
+ includeModel: { type: "boolean", default: true }
146
+ }
147
+ },
148
+ permission: "auto",
149
+ mutating: false,
150
+ async execute(input) {
151
+ const format = input["format"] ?? "json";
152
+ const includeModel = input["includeModel"] ?? true;
153
+ if (format === "csv") {
154
+ const header = includeModel ? "model,timestamp,prompt_tokens,completion_tokens,total_tokens,cost_usd" : "timestamp,prompt_tokens,completion_tokens,total_tokens,cost_usd";
155
+ const rows = sessionCost.requests.map(
156
+ (r) => includeModel ? `${r.model},${r.timestamp},${r.promptTokens},${r.completionTokens},${r.totalTokens},${r.costUsd ?? 0}` : `${r.timestamp},${r.promptTokens},${r.completionTokens},${r.totalTokens},${r.costUsd ?? 0}`
157
+ );
158
+ return {
159
+ ok: true,
160
+ format: "csv",
161
+ data: [header, ...rows].join("\n"),
162
+ summary: {
163
+ totalTokens: sessionCost.totalTokens,
164
+ totalCostUsd: sessionCost.totalCostUsd,
165
+ totalRequests: sessionCost.requests.length
166
+ }
167
+ };
168
+ }
169
+ return {
170
+ ok: true,
171
+ format: "json",
172
+ data: {
173
+ summary: {
174
+ totalTokens: sessionCost.totalTokens,
175
+ totalPromptTokens: sessionCost.totalPromptTokens,
176
+ totalCompletionTokens: sessionCost.totalCompletionTokens,
177
+ totalCostUsd: sessionCost.totalCostUsd,
178
+ totalRequests: sessionCost.requests.length,
179
+ byModel: sessionCost.byModel
180
+ },
181
+ requests: includeModel ? sessionCost.requests : sessionCost.requests.map(({ promptTokens, completionTokens, totalTokens, costUsd, timestamp }) => ({
182
+ promptTokens,
183
+ completionTokens,
184
+ totalTokens,
185
+ costUsd,
186
+ timestamp
187
+ }))
188
+ }
189
+ };
190
+ }
191
+ });
192
+ api.onEvent("session.close", async () => {
193
+ if (sessionCost.requests.length > 0) {
194
+ await api.session.append({
195
+ type: "cost-tracker:session_summary",
196
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
197
+ totalTokens: sessionCost.totalTokens,
198
+ totalCostUsd: sessionCost.totalCostUsd,
199
+ totalRequests: sessionCost.requests.length,
200
+ byModel: sessionCost.byModel
201
+ });
202
+ }
203
+ });
204
+ api.log.info("cost-tracker plugin loaded", { version: "0.1.0" });
205
+ }
206
+ };
207
+ var cost_tracker_default = plugin;
208
+
209
+ export { cost_tracker_default as default };
package/dist/cron.d.ts ADDED
@@ -0,0 +1,14 @@
1
+ import { Plugin } from '@wrongstack/core';
2
+
3
+ /**
4
+ * cron plugin — Schedules recurring tasks via beforeIteration extension hooks.
5
+ *
6
+ * Tools registered:
7
+ * - cron_schedule: Schedule a recurring action
8
+ * - cron_list: List all scheduled jobs
9
+ * - cron_cancel: Cancel a scheduled job
10
+ */
11
+
12
+ declare const plugin: Plugin;
13
+
14
+ export { plugin as default };