@wrongstack/plugins 0.275.1 → 0.276.3

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/dist/index.js CHANGED
@@ -2,9 +2,7 @@ import { execFileSync, execSync } from 'child_process';
2
2
  import { watch, existsSync, readFileSync, writeFileSync, readdirSync } from 'fs';
3
3
  import * as path from 'path';
4
4
  import { isAbsolute, join } from 'path';
5
- import { expectDefined, deepMerge as deepMerge$1, isPrivateIPv4, isPrivateIPv6 } from '@wrongstack/core';
6
- import { lookup } from 'dns/promises';
7
- import { isIPv4, isIPv6 } from 'net';
5
+ import { expectDefined } from '@wrongstack/core';
8
6
  import { toErrorMessage } from '@wrongstack/core/utils';
9
7
 
10
8
  // src/auto-doc/index.ts
@@ -126,7 +124,7 @@ async function runAutoDoc(input, api) {
126
124
  modified = injectDocComment(modified, entity, doc);
127
125
  results.push({ file, entity: entity.name });
128
126
  }
129
- if (!input.dryRun && results.length > 0) {
127
+ if (!input.dry_run && results.length > 0) {
130
128
  writeFileSync2(file, modified, "utf-8");
131
129
  api.log.info(`auto-doc: updated ${file}`);
132
130
  }
@@ -136,31 +134,9 @@ async function runAutoDoc(input, api) {
136
134
  }
137
135
  return { ok: true, filesProcessed: input.files.length, changes: results };
138
136
  }
139
- async function runAutoDocPreview(input, api) {
140
- if (!input.files || typeof input.files !== "object" || !Array.isArray(input.files)) {
141
- return { ok: false, error: "input.files must be an array of file paths", previews: [] };
142
- }
143
- if (input.files.length === 0) {
144
- return { ok: false, error: "input.files is empty \u2014 provide at least one file path", previews: [] };
145
- }
146
- const includeTypes = api.config.extensions?.["auto-doc"]?.["includeTypes"] ?? false;
147
- const previews = [];
148
- for (const file of input.files) {
149
- try {
150
- const { readFileSync: readFileSync2 } = await import('fs');
151
- const content = readFileSync2(file, "utf-8");
152
- const entities = parseSource(content);
153
- const generated = entities.filter((e) => needsDocComment(content, e)).map((e) => generateDocComment(e, includeTypes));
154
- previews.push({ file, entities: generated });
155
- } catch {
156
- api.log.warn(`auto-doc-preview: could not read file ${file}`);
157
- }
158
- }
159
- return { ok: true, previews };
160
- }
161
137
  var plugin = {
162
138
  name: "auto-doc",
163
- version: "0.1.0",
139
+ version: "0.2.0",
164
140
  description: "Auto-generates JSDoc/TSDoc comments for functions, classes, types, and interfaces",
165
141
  apiVersion: AUTO_DOC_API_VERSION,
166
142
  capabilities: { tools: true, pipelines: ["toolCall"] },
@@ -176,41 +152,25 @@ var plugin = {
176
152
  setup(api) {
177
153
  api.tools.register({
178
154
  name: "auto_doc",
179
- description: "Auto-generate JSDoc/TSDoc comments for functions, classes, types, and interfaces in source files",
155
+ description: "Auto-generate JSDoc/TSDoc comments for functions, classes, types, and interfaces in source files. Set `dry_run: true` to preview without writing.",
180
156
  inputSchema: {
181
157
  type: "object",
182
158
  properties: {
183
159
  files: { type: "array", items: { type: "string" }, description: "Source files to document" },
184
160
  style: { type: "string", enum: ["jsdoc", "tsdoc"], default: "tsdoc", description: "Comment style" },
185
161
  force: { type: "boolean", default: false, description: "Overwrite existing docstrings" },
186
- dryRun: { type: "boolean", default: false, description: "Preview without writing" }
162
+ dry_run: { type: "boolean", default: false, description: "Preview generated comments without writing to files" }
187
163
  },
188
164
  required: ["files"]
189
165
  },
190
166
  permission: "auto",
191
167
  mutating: true,
168
+ category: "Project",
192
169
  async execute(input) {
193
170
  return runAutoDoc(input, api);
194
171
  }
195
172
  });
196
- api.tools.register({
197
- name: "auto_doc_preview",
198
- description: "Preview what JSDoc/TSDoc comments would be generated for files, without writing",
199
- inputSchema: {
200
- type: "object",
201
- properties: {
202
- files: { type: "array", items: { type: "string" }, description: "Source files to preview" },
203
- style: { type: "string", enum: ["jsdoc", "tsdoc"], default: "tsdoc" }
204
- },
205
- required: ["files"]
206
- },
207
- permission: "auto",
208
- mutating: false,
209
- async execute(input) {
210
- return runAutoDocPreview(input, api);
211
- }
212
- });
213
- api.log.info("auto-doc plugin loaded", { version: "0.1.0", capabilities: ["auto_doc", "auto_doc_preview"] });
173
+ api.log.info("auto-doc plugin loaded", { version: "0.2.0", capabilities: ["auto_doc"] });
214
174
  },
215
175
  teardown(api) {
216
176
  api.log.info("auto-doc plugin unloaded");
@@ -257,19 +217,6 @@ function stageFiles(files, cwd) {
257
217
  function commitWithMessage(message, cwd) {
258
218
  return runGit(["commit", "-m", message], cwd);
259
219
  }
260
- function getCommitHistory(since, cwd) {
261
- const range = `${since}..HEAD` ;
262
- const output = runGit(["log", range, "--format=%H %s"], cwd);
263
- if (!output) return [];
264
- return output.split("\n").filter(Boolean).map((line) => {
265
- const spaceIdx = line.indexOf(" ");
266
- const hash = line.slice(0, spaceIdx);
267
- const message = line.slice(spaceIdx + 1);
268
- const typeMatch = message.match(/^(\w+)(!)?:\s/);
269
- const type = typeMatch?.[1] ?? "chore";
270
- return { hash, message, type };
271
- });
272
- }
273
220
  function getWorktrees(cwd) {
274
221
  try {
275
222
  const out = runGit(["worktree", "list", "--porcelain"], cwd);
@@ -333,7 +280,7 @@ ${body}` : "";
333
280
  }
334
281
  var plugin2 = {
335
282
  name: "git-autocommit",
336
- version: "0.1.0",
283
+ version: "0.2.0",
337
284
  description: "AI-powered git staging and conventional commit message generation",
338
285
  apiVersion: API_VERSION,
339
286
  capabilities: { tools: true },
@@ -376,10 +323,11 @@ var plugin2 = {
376
323
  scope: { type: "string", description: "Commit scope (e.g. auth, api, ui)" },
377
324
  message: { type: "string", description: "Commit summary message" },
378
325
  body: { type: "string", description: "Optional commit body/description" },
379
- dryRun: { type: "boolean", default: false, description: "Show what would be committed without committing" }
326
+ dry_run: { type: "boolean", default: false, description: "Show what would be committed without committing" }
380
327
  }
381
328
  },
382
329
  permission: "confirm",
330
+ category: "Git",
383
331
  mutating: true,
384
332
  async execute(input, _ctx) {
385
333
  try {
@@ -387,13 +335,13 @@ var plugin2 = {
387
335
  const scope = input["scope"];
388
336
  const summary = input["message"] ?? "";
389
337
  const body = input["body"];
390
- const dryRun = input["dryRun"] ?? false;
338
+ const dryRun = input["dry_run"] ?? false;
391
339
  const validTypes = ["feat", "fix", "docs", "style", "refactor", "test", "chore", "perf", "ci", "build", "revert"];
392
340
  if (!type || !validTypes.includes(type)) {
393
341
  if (dryRun) {
394
342
  return {
395
343
  ok: true,
396
- dryRun: true,
344
+ dry_run: true,
397
345
  message: `Would create: ${summary || "update code"}`
398
346
  };
399
347
  }
@@ -454,7 +402,7 @@ var plugin2 = {
454
402
  if (dryRun) {
455
403
  return {
456
404
  ok: true,
457
- dryRun: true,
405
+ dry_run: true,
458
406
  message: `Would create: ${msg}`,
459
407
  warning: warning ?? void 0,
460
408
  stagedDiff: `
@@ -516,126 +464,8 @@ ${preCommitDiff}
516
464
  }
517
465
  }
518
466
  });
519
- api.tools.register({
520
- name: "git_stage",
521
- description: "Stage specific files for commit. Shows what would be staged without staging if dryRun is true.",
522
- inputSchema: {
523
- type: "object",
524
- properties: {
525
- files: { type: "array", items: { type: "string" }, description: "Files to stage" },
526
- dryRun: { type: "boolean", default: false }
527
- },
528
- required: ["files"]
529
- },
530
- permission: "confirm",
531
- mutating: true,
532
- async execute(input) {
533
- try {
534
- let files;
535
- try {
536
- files = input["files"] ?? [];
537
- } catch {
538
- files = [];
539
- }
540
- const dryRun = input["dryRun"] ?? false;
541
- if (!Array.isArray(files) || files.length === 0) {
542
- return { ok: false, error: "files must be a non-empty array of file paths" };
543
- }
544
- if (dryRun) {
545
- return { ok: true, dryRun: true, files, message: `Would stage: ${files.join(", ")}` };
546
- }
547
- try {
548
- stageFiles(files);
549
- } catch (err) {
550
- return { ok: false, error: `Failed to stage files: ${err instanceof Error ? err.message : String(err)}` };
551
- }
552
- let stillChanged = [];
553
- try {
554
- stillChanged = getChangedFiles();
555
- } catch {
556
- stillChanged = [];
557
- }
558
- return {
559
- ok: true,
560
- staged: files,
561
- stillChanged,
562
- message: `Staged ${files.length} file(s). ${stillChanged.length} file(s) still changed.`
563
- };
564
- } catch (err) {
565
- return { ok: false, error: `git_stage error: ${err instanceof Error ? err.message : String(err)}` };
566
- }
567
- }
568
- });
569
- api.tools.register({
570
- name: "git_status_summary",
571
- description: "Returns a summary of the current git repository status: changed files, staged files, current branch, and recent commits.",
572
- inputSchema: { type: "object", properties: {} },
573
- permission: "auto",
574
- mutating: false,
575
- async execute() {
576
- let branch = "";
577
- let changed = [];
578
- let staged = [];
579
- let aheadBehind = "";
580
- const recentCommits = [];
581
- let worktrees = [];
582
- let worktreeWarn = null;
583
- let externalChanges = null;
584
- try {
585
- branch = runGit(["branch", "--show-current"]);
586
- } catch {
587
- }
588
- try {
589
- changed = getChangedFiles();
590
- } catch {
591
- }
592
- try {
593
- staged = getStagedFiles();
594
- } catch {
595
- }
596
- try {
597
- aheadBehind = runGit(["status", "-sb"]).split("\n")[0] ?? "";
598
- } catch {
599
- }
600
- try {
601
- recentCommits.push(...getCommitHistory("-3", void 0).map((c) => ({ hash: (c.hash ?? "").slice(0, 7), message: c.message })));
602
- } catch {
603
- }
604
- try {
605
- worktrees = getWorktrees();
606
- } catch {
607
- }
608
- try {
609
- worktreeWarn = simultaneousEditWarning();
610
- } catch {
611
- }
612
- try {
613
- const out = runGit(["status", "--porcelain"]);
614
- const unstaged = out.split("\n").filter((l) => {
615
- const idx = l[0] ?? " ";
616
- return idx === " " || idx === "?";
617
- }).map((l) => l.slice(3).trim()).filter(Boolean);
618
- externalChanges = unstaged.length > 0 ? unstaged : null;
619
- } catch {
620
- }
621
- return {
622
- ok: true,
623
- branch,
624
- changedFiles: changed,
625
- stagedFiles: staged,
626
- aheadBehind,
627
- recentCommits,
628
- worktrees: worktrees.length > 0 ? worktrees.map((w) => ({
629
- path: w.path,
630
- branch: w.branch.replace("refs/heads/", "")
631
- })) : [],
632
- worktreeWarning: worktreeWarn ?? void 0,
633
- externalChanges: externalChanges ?? void 0
634
- };
635
- }
636
- });
637
467
  api.log.info("git-autocommit plugin loaded", {
638
- version: "0.1.0",
468
+ version: "0.2.0",
639
469
  conventionalCommits: opts.conventionalCommits
640
470
  });
641
471
  }
@@ -710,7 +540,7 @@ function findShellFiles(dir, pattern) {
710
540
  }
711
541
  var plugin3 = {
712
542
  name: "shell-check",
713
- version: "0.1.0",
543
+ version: "0.2.0",
714
544
  description: "Runs shellcheck analysis on bash/shell scripts and surfaces issues with severity levels",
715
545
  apiVersion: API_VERSION2,
716
546
  capabilities: { tools: true, pipelines: ["toolCall"] },
@@ -732,14 +562,24 @@ var plugin3 = {
732
562
  setup(api) {
733
563
  api.tools.register({
734
564
  name: "shellcheck",
735
- description: "Run shellcheck analysis on shell script files. Returns issues with file, line, column, severity, code, and message.",
565
+ description: "Run shellcheck analysis on shell script files. Pass `files` for specific files, or `directory` (optionally with `pattern`) to recursively scan for .sh files. Returns issues with file, line, column, severity, code, and message.",
736
566
  inputSchema: {
737
567
  type: "object",
738
568
  properties: {
739
569
  files: {
740
570
  type: "array",
741
571
  items: { type: "string" },
742
- description: "Shell script files to check"
572
+ description: "Shell script files to check. Mutually exclusive with `directory`."
573
+ },
574
+ directory: {
575
+ type: "string",
576
+ default: ".",
577
+ description: "Directory to recursively scan for .sh files. Used when `files` is omitted."
578
+ },
579
+ pattern: {
580
+ type: "string",
581
+ default: "",
582
+ description: "Filename pattern to match when scanning a directory (default: all .sh files)."
743
583
  },
744
584
  severity: {
745
585
  type: "string",
@@ -752,20 +592,39 @@ var plugin3 = {
752
592
  default: false,
753
593
  description: "Apply safe automatic fixes where possible"
754
594
  }
755
- },
756
- required: ["files"]
595
+ }
757
596
  },
758
597
  permission: "auto",
598
+ category: "Code Quality",
759
599
  mutating: true,
760
600
  async execute(input) {
761
601
  const files = input["files"];
602
+ const directory = input["directory"] ?? ".";
603
+ const pattern = input["pattern"] ?? "";
762
604
  const severity = input["severity"] ?? "warning";
605
+ let checkFiles;
606
+ let scannedDirectories = false;
607
+ if (files && files.length > 0) {
608
+ checkFiles = files;
609
+ } else {
610
+ checkFiles = findShellFiles(directory, pattern);
611
+ scannedDirectories = true;
612
+ }
613
+ if (checkFiles.length === 0) {
614
+ return {
615
+ ok: true,
616
+ filesScanned: 0,
617
+ issues: [],
618
+ summary: { total: 0 },
619
+ mode: scannedDirectories ? "directory" : "files"
620
+ };
621
+ }
763
622
  let issues;
764
623
  try {
765
- issues = runShellCheck(files, severity);
624
+ issues = runShellCheck(checkFiles, severity);
766
625
  } catch (err) {
767
626
  const msg = err instanceof Error ? err.message : String(err);
768
- return { ok: false, error: msg, issues: [] };
627
+ return { ok: false, error: msg, issues: [], filesScanned: 0 };
769
628
  }
770
629
  const byFile = {};
771
630
  for (const issue of issues) {
@@ -779,10 +638,12 @@ var plugin3 = {
779
638
  const infoCount = issues.filter((i) => i.level === "info").length;
780
639
  const styleCount = issues.filter((i) => i.level === "style").length;
781
640
  api.metrics.counter("issues_found", issues.length, { severity });
782
- api.metrics.histogram("issues_per_file", issues.length / Math.max(files.length, 1));
641
+ api.metrics.histogram("issues_per_file", issues.length / Math.max(checkFiles.length, 1));
783
642
  return {
784
643
  ok: true,
785
- filesScanned: files.length,
644
+ mode: scannedDirectories ? "directory" : "files",
645
+ filesScanned: checkFiles.length,
646
+ filesWithIssues: Object.keys(byFile).length,
786
647
  issues,
787
648
  summary: {
788
649
  total: issues.length,
@@ -796,68 +657,7 @@ var plugin3 = {
796
657
  };
797
658
  }
798
659
  });
799
- api.tools.register({
800
- name: "shellcheck_scan",
801
- description: "Recursively scan a directory for shell scripts and run shellcheck on all found files.",
802
- inputSchema: {
803
- type: "object",
804
- properties: {
805
- directory: {
806
- type: "string",
807
- default: ".",
808
- description: "Directory to scan"
809
- },
810
- pattern: {
811
- type: "string",
812
- default: "",
813
- description: "Filename pattern to match (default: all .sh files)"
814
- },
815
- severity: {
816
- type: "string",
817
- enum: ["error", "warning", "info", "style"],
818
- default: "warning"
819
- }
820
- }
821
- },
822
- permission: "auto",
823
- mutating: true,
824
- async execute(input) {
825
- const dir = input["directory"] ?? ".";
826
- const pattern = input["pattern"] ?? "";
827
- const severity = input["severity"] ?? "warning";
828
- const files = findShellFiles(dir, pattern);
829
- if (files.length === 0) {
830
- return { ok: true, filesScanned: 0, issues: [], summary: { total: 0 } };
831
- }
832
- let issues;
833
- try {
834
- issues = runShellCheck(files, severity);
835
- } catch (err) {
836
- const msg = err instanceof Error ? err.message : String(err);
837
- return { ok: false, error: msg, issues: [], filesScanned: 0 };
838
- }
839
- const byFile = {};
840
- for (const issue of issues) {
841
- if (byFile[issue.file] === void 0) {
842
- byFile[issue.file] = [];
843
- }
844
- byFile[issue.file]?.push(issue);
845
- }
846
- return {
847
- ok: true,
848
- filesScanned: files.length,
849
- filesWithIssues: Object.keys(byFile).length,
850
- issues,
851
- summary: {
852
- total: issues.length,
853
- errors: issues.filter((i) => i.level === "error").length,
854
- warnings: issues.filter((i) => i.level === "warning").length
855
- },
856
- byFile
857
- };
858
- }
859
- });
860
- api.log.info("shell-check plugin loaded", { version: "0.1.0" });
660
+ api.log.info("shell-check plugin loaded", { version: "0.2.0" });
861
661
  }
862
662
  };
863
663
  var shell_check_default = plugin3;
@@ -951,6 +751,7 @@ var plugin4 = {
951
751
  description: "Returns the current session's token usage breakdown by model, total cost estimate, and budget status.",
952
752
  inputSchema: { type: "object", properties: {} },
953
753
  permission: "auto",
754
+ category: "Meta",
954
755
  mutating: false,
955
756
  async execute() {
956
757
  const { budgetLimit, warningThreshold } = readCostTrackerConfig(
@@ -1079,9 +880,9 @@ var plugin4 = {
1079
880
  };
1080
881
  var cost_tracker_default = plugin4;
1081
882
  var API_VERSION4 = "^0.1.10";
1082
- var watchIdCounter = 0;
883
+ var watch_idCounter = 0;
1083
884
  function nextId() {
1084
- return `watch_${++watchIdCounter}_${Date.now().toString(36)}`;
885
+ return `watch_${++watch_idCounter}_${Date.now().toString(36)}`;
1085
886
  }
1086
887
  var watches = /* @__PURE__ */ new Map();
1087
888
  var debounceTimers = /* @__PURE__ */ new Map();
@@ -1166,7 +967,7 @@ var plugin5 = {
1166
967
  const key = `${handle.id}:${fullPath}:${eventType}`;
1167
968
  debounceEvent(key, () => {
1168
969
  api.emitCustom("file-watcher:changed", {
1169
- watchId: handle.id,
970
+ watch_id: handle.id,
1170
971
  path: fullPath,
1171
972
  event: eventType,
1172
973
  filename,
@@ -1227,15 +1028,16 @@ var plugin5 = {
1227
1028
  required: ["paths"]
1228
1029
  },
1229
1030
  permission: "confirm",
1031
+ category: "Filesystem",
1230
1032
  mutating: false,
1231
1033
  async execute(input) {
1232
1034
  const rawPaths = input["paths"];
1233
1035
  if (!rawPaths || typeof rawPaths !== "object" || !Array.isArray(rawPaths)) {
1234
- return { ok: false, error: "paths must be an array of file/directory paths", watchId: null };
1036
+ return { ok: false, error: "paths must be an array of file/directory paths", watch_id: null };
1235
1037
  }
1236
1038
  const paths = rawPaths;
1237
1039
  if (paths.length === 0) {
1238
- return { ok: false, error: "paths array is empty \u2014 provide at least one path", watchId: null };
1040
+ return { ok: false, error: "paths array is empty \u2014 provide at least one path", watch_id: null };
1239
1041
  }
1240
1042
  const events = input["events"] ?? ["change", "add", "delete"];
1241
1043
  const recursive = input["recursive"] ?? true;
@@ -1255,7 +1057,7 @@ var plugin5 = {
1255
1057
  api.metrics.gauge("active_watches", watches.size);
1256
1058
  return {
1257
1059
  ok: true,
1258
- watchId: id,
1060
+ watch_id: id,
1259
1061
  paths,
1260
1062
  events,
1261
1063
  recursive,
@@ -1269,17 +1071,17 @@ var plugin5 = {
1269
1071
  inputSchema: {
1270
1072
  type: "object",
1271
1073
  properties: {
1272
- watchId: { type: "string", description: "Watch ID returned by watch_start" }
1074
+ watch_id: { type: "string", description: "Watch ID returned by watch_start" }
1273
1075
  },
1274
- required: ["watchId"]
1076
+ required: ["watch_id"]
1275
1077
  },
1276
1078
  permission: "auto",
1277
1079
  mutating: false,
1278
1080
  async execute(input) {
1279
- const watchId = input["watchId"];
1280
- const handle = watches.get(watchId);
1081
+ const watch_id = input["watch_id"];
1082
+ const handle = watches.get(watch_id);
1281
1083
  if (!handle) {
1282
- return { ok: false, error: `No active watch with ID: ${watchId}` };
1084
+ return { ok: false, error: `No active watch with ID: ${watch_id}` };
1283
1085
  }
1284
1086
  for (const w of handle.watchers) {
1285
1087
  try {
@@ -1287,12 +1089,12 @@ var plugin5 = {
1287
1089
  } catch {
1288
1090
  }
1289
1091
  }
1290
- watches.delete(watchId);
1092
+ watches.delete(watch_id);
1291
1093
  api.metrics.gauge("active_watches", watches.size);
1292
1094
  return {
1293
1095
  ok: true,
1294
- watchId,
1295
- message: `Stopped watch ${watchId}. ${watches.size} watch(es) remaining.`
1096
+ watch_id,
1097
+ message: `Stopped watch ${watch_id}. ${watches.size} watch(es) remaining.`
1296
1098
  };
1297
1099
  }
1298
1100
  });
@@ -1339,568 +1141,31 @@ var plugin5 = {
1339
1141
  }
1340
1142
  };
1341
1143
  var file_watcher_default = plugin5;
1342
- var API_VERSION5 = "^0.1.10";
1343
- async function duckduckgoSearch(query, numResults) {
1344
- const url = `https://html.duckduckgo.com/html/?q=${encodeURIComponent(query)}&kl=us-en`;
1345
- const resp = await fetch(url, {
1346
- headers: {
1347
- "User-Agent": "Mozilla/5.0 (compatible; WrongStack/1.0; +https://wrongstack.com)"
1348
- }
1349
- });
1350
- if (!resp.ok) throw new Error(`DuckDuckGo search failed: ${resp.status}`);
1351
- const html = await resp.text();
1352
- const results = [];
1353
- const resultRe = /<a\b[^>]*class="[^"]*result__a[^"]*"[^>]*href="([^"]+)"[^>]*>([\s\S]*?)<\/a>([\s\S]*?)(?=<a\b[^>]*class="[^"]*result__a[^"]*"|<\/body>|$)/gi;
1354
- let m;
1355
- while ((m = resultRe.exec(html)) !== null && results.length < numResults) {
1356
- const url2 = normalizeDuckDuckGoUrl(m[1]);
1357
- if (!url2) continue;
1358
- const title = htmlToText(m[2] ?? "");
1359
- const snippet = extractDuckDuckGoSnippet(m[3] ?? "");
1360
- results.push({
1361
- url: url2,
1362
- title,
1363
- snippet,
1364
- score: 1,
1365
- source: "duckduckgo",
1366
- cached: false
1367
- });
1368
- }
1369
- if (results.length === 0 && /result__a|result__snippet|result__body|anomaly-modal|captcha/i.test(html)) {
1370
- throw new Error("DuckDuckGo response format was not recognized or was blocked");
1371
- }
1372
- return results;
1373
- }
1374
- function normalizeDuckDuckGoUrl(rawUrl) {
1375
- if (!rawUrl) return null;
1376
- let url = htmlToText(rawUrl);
1377
- if (url.startsWith("//duckduckgo.com/l/")) url = `https:${url}`;
1378
- if (url.startsWith("/l/")) url = new URL(url, "https://duckduckgo.com").toString();
1379
- try {
1380
- const parsed = new URL(url);
1381
- const redirected = parsed.searchParams.get("uddg");
1382
- if (redirected) return redirected;
1383
- return parsed.toString();
1384
- } catch {
1385
- return null;
1386
- }
1387
- }
1388
- function htmlToText(html) {
1389
- return html.replace(/<[^>]+>/g, "").replace(/&amp;/g, "&").replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&#39;/g, "'").replace(/\s+/g, " ").trim();
1390
- }
1391
- function extractDuckDuckGoSnippet(html) {
1392
- const snippetRe = /<(?:a|div)\b[^>]*class="[^"]*(?:result__snippet|result__body)[^"]*"[^>]*>([\s\S]*?)<\/(?:a|div)>/i;
1393
- const snippetMatch = snippetRe.exec(html);
1394
- return htmlToText(snippetMatch?.[1] ?? "");
1395
- }
1396
- function assertSafeIp(ip) {
1397
- if (isIPv4(ip) && isPrivateIPv4(ip)) {
1398
- throw new Error(`Blocked private/loopback address: ${ip}`);
1399
- }
1400
- if (isIPv6(ip) && isPrivateIPv6(ip)) {
1401
- throw new Error(`Blocked private/loopback address: ${ip}`);
1402
- }
1403
- }
1404
- async function assertSafeUrl(rawUrl) {
1405
- const u = new URL(rawUrl);
1406
- if (u.protocol !== "http:" && u.protocol !== "https:") {
1407
- throw new Error(`Unsupported protocol: ${u.protocol}`);
1408
- }
1409
- const host = u.hostname.startsWith("[") && u.hostname.endsWith("]") ? u.hostname.slice(1, -1) : u.hostname;
1410
- if (host === "localhost" || host.endsWith(".localhost") || host === "" || host === "0.0.0.0") {
1411
- throw new Error("Blocked localhost target");
1412
- }
1413
- if (isIPv4(host) || isIPv6(host)) {
1414
- assertSafeIp(host);
1415
- return;
1416
- }
1417
- let addrs;
1418
- try {
1419
- addrs = await lookup(host, { all: true });
1420
- } catch {
1421
- throw new Error(`Could not resolve host: ${host}`);
1422
- }
1423
- for (const { address } of addrs) assertSafeIp(address);
1424
- }
1425
- async function fetchUrl(url, format) {
1426
- const MAX_REDIRECTS = 5;
1427
- let currentUrl = url;
1428
- let resp;
1429
- for (let i = 0; i <= MAX_REDIRECTS; i++) {
1430
- await assertSafeUrl(currentUrl);
1431
- resp = await fetch(currentUrl, {
1432
- redirect: "manual",
1433
- headers: {
1434
- "User-Agent": "Mozilla/5.0 (compatible; WrongStack/1.0; +https://wrongstack.com)",
1435
- Accept: format === "text" ? "text/plain" : "text/html"
1436
- }
1437
- });
1438
- if (resp.status >= 300 && resp.status < 400) {
1439
- const loc = resp.headers.get("location");
1440
- if (!loc) break;
1441
- currentUrl = new URL(loc, currentUrl).toString();
1442
- if (i === MAX_REDIRECTS) throw new Error("Too many redirects");
1443
- continue;
1444
- }
1445
- break;
1446
- }
1447
- if (!resp) throw new Error(`Failed to fetch ${url}`);
1448
- if (!resp.ok) throw new Error(`Failed to fetch ${url}: ${resp.status} ${resp.statusText}`);
1449
- if (format === "text") {
1450
- return resp.text();
1451
- }
1452
- let html = await resp.text();
1453
- html = html.replace(/<script[^>]*>[\s\S]*?<\/script>/gi, "");
1454
- html = html.replace(/<style[^>]*>[\s\S]*?<\/style>/gi, "");
1455
- html = html.replace(/<h[1-6][^>]*>([\s\S]*?)<\/h[1-6]>/gi, (_, t) => `
1456
- ## ${t.trim()}
1457
- `);
1458
- html = html.replace(/<p[^>]*>([\s\S]*?)<\/p>/gi, (_, t) => `${t.trim()}
1459
1144
 
1460
- `);
1461
- html = html.replace(/<br\s*\/?>/gi, "\n");
1462
- html = html.replace(/<[^>]+>/g, "");
1463
- html = html.replace(/&amp;/g, "&");
1464
- html = html.replace(/&lt;/g, "<");
1465
- html = html.replace(/&gt;/g, ">");
1466
- html = html.replace(/&quot;/g, '"');
1467
- html = html.replace(/&#39;/g, "'");
1468
- html = html.replace(/\n{3,}/g, "\n\n");
1469
- return html.trim().slice(0, 5e4);
1470
- }
1471
- function scoreResults(results, query) {
1472
- const terms = query.toLowerCase().split(/\s+/);
1473
- return results.map((r) => {
1474
- const titleLower = r.title.toLowerCase();
1475
- const snippetLower = r.snippet.toLowerCase();
1476
- let score = r.score;
1477
- for (const term of terms) {
1478
- if (titleLower.includes(term)) score += 2;
1479
- if (snippetLower.includes(term)) score += 1;
1480
- }
1481
- return { ...r, score };
1482
- }).sort((a, b) => b.score - a.score);
1483
- }
1145
+ // src/web-search/index.ts
1146
+ var API_VERSION5 = "^0.1.10";
1484
1147
  var plugin6 = {
1485
1148
  name: "web-search",
1486
- version: "0.1.0",
1487
- description: "Cached web search with deduplication and relevance ranking",
1149
+ version: "0.3.0",
1150
+ description: "Retired \u2014 capabilities merged into built-in search and fetch tools",
1488
1151
  apiVersion: API_VERSION5,
1489
- capabilities: { tools: true, pipelines: ["request"] },
1490
- defaultConfig: {
1491
- cacheTtlMs: 3e5,
1492
- maxResults: 10,
1493
- userAgent: "WrongStack/1.0"
1494
- },
1495
- configSchema: {
1496
- type: "object",
1497
- properties: {
1498
- cacheTtlMs: { type: "number", default: 3e5 },
1499
- maxResults: { type: "number", default: 10 },
1500
- userAgent: { type: "string", default: "WrongStack/1.0" }
1501
- }
1502
- },
1152
+ capabilities: { tools: true },
1503
1153
  setup(api) {
1504
- const cache = /* @__PURE__ */ new Map();
1505
- const cacheTtlMs = api.config.extensions?.["web-search"]?.["cacheTtlMs"] ?? 3e5;
1506
- const maxResults = api.config.extensions?.["web-search"]?.["maxResults"] ?? 10;
1507
- api.tools.register({
1508
- name: "web_search",
1509
- description: "Search the web using DuckDuckGo with automatic caching and deduplication. Results are cached for faster subsequent queries.",
1510
- inputSchema: {
1511
- type: "object",
1512
- properties: {
1513
- query: { type: "string", description: "Search query" },
1514
- numResults: { type: "number", default: 10, description: "Maximum number of results" },
1515
- source: { type: "string", enum: ["duckduckgo"], default: "duckduckgo", description: "Search engine" },
1516
- skipCache: { type: "boolean", default: false, description: "Skip cache and force fresh search" }
1517
- },
1518
- required: ["query"]
1519
- },
1520
- permission: "auto",
1521
- mutating: true,
1522
- async execute(input) {
1523
- const query = input["query"];
1524
- if (!query || typeof query !== "string" || query.trim() === "") {
1525
- return { ok: false, error: "query is required and must be a non-empty string", results: [] };
1526
- }
1527
- const numResults = input["numResults"] ?? maxResults;
1528
- const skipCache = input["skipCache"] ?? false;
1529
- if (!skipCache) {
1530
- const cached = cache.get(query);
1531
- if (cached && Date.now() - cached.timestamp < cacheTtlMs) {
1532
- const results = cached.results.map((r) => ({ ...r, cached: true }));
1533
- api.metrics.counter("cache_hit", 1, { query: query.slice(0, 20) });
1534
- return {
1535
- ok: true,
1536
- query,
1537
- cached: true,
1538
- results: results.slice(0, numResults),
1539
- count: results.length
1540
- };
1541
- }
1542
- }
1543
- api.metrics.counter("cache_miss", 1, { query: query.slice(0, 20) });
1544
- const seenUrls = /* @__PURE__ */ new Set();
1545
- let rawResults;
1546
- try {
1547
- rawResults = await duckduckgoSearch(query, numResults * 2);
1548
- } catch (err) {
1549
- const msg = err instanceof Error ? err.message : String(err);
1550
- return { ok: false, error: `Search failed: ${msg}`, results: [] };
1551
- }
1552
- const deduplicated = [];
1553
- for (const r of rawResults) {
1554
- const noQuery = r.url.split("?")[0] ?? r.url;
1555
- const normalized = noQuery.split("#")[0] ?? r.url;
1556
- if (!seenUrls.has(normalized) && r.url.startsWith("http")) {
1557
- seenUrls.add(normalized);
1558
- deduplicated.push(r);
1559
- }
1560
- }
1561
- const ranked = scoreResults(deduplicated, query);
1562
- cache.set(query, { results: ranked, timestamp: Date.now() });
1563
- const now = Date.now();
1564
- for (const [key, entry] of cache.entries()) {
1565
- if (now - entry.timestamp > cacheTtlMs * 2) cache.delete(key);
1566
- }
1567
- return {
1568
- ok: true,
1569
- query,
1570
- cached: false,
1571
- results: ranked.slice(0, numResults),
1572
- count: ranked.length
1573
- };
1574
- }
1575
- });
1576
- api.tools.register({
1577
- name: "web_fetch",
1578
- description: "Fetch a URL and return its content as markdown or plain text.",
1579
- inputSchema: {
1580
- type: "object",
1581
- properties: {
1582
- url: { type: "string", format: "uri", description: "URL to fetch" },
1583
- format: { type: "string", enum: ["markdown", "text"], default: "markdown" }
1584
- },
1585
- required: ["url"]
1586
- },
1587
- permission: "confirm",
1588
- mutating: false,
1589
- async execute(input) {
1590
- const rawUrl = input["url"];
1591
- if (!rawUrl || typeof rawUrl !== "string") {
1592
- return { ok: false, error: "url is required and must be a string" };
1593
- }
1594
- const url = rawUrl;
1595
- const format = input["format"] ?? "markdown";
1596
- if (!url.startsWith("http://") && !url.startsWith("https://")) {
1597
- return { ok: false, error: "URL must start with http:// or https://" };
1598
- }
1599
- let content;
1600
- try {
1601
- content = await fetchUrl(url, format);
1602
- } catch (err) {
1603
- const msg = err instanceof Error ? err.message : String(err);
1604
- return { ok: false, error: msg };
1605
- }
1606
- return {
1607
- ok: true,
1608
- url,
1609
- format,
1610
- contentLength: content.length,
1611
- content: content.slice(0, 2e4),
1612
- truncated: content.length > 2e4
1613
- };
1614
- }
1615
- });
1616
- api.log.info("web-search plugin loaded", { version: "0.1.0", cacheTtlMs });
1154
+ api.log.info("web-search plugin retired \u2014 use the built-in search and fetch tools");
1617
1155
  }
1618
1156
  };
1619
1157
  var web_search_default = plugin6;
1158
+
1159
+ // src/json-path/index.ts
1620
1160
  var API_VERSION6 = "^0.1.10";
1621
- function jmespathSearch(data, query) {
1622
- if (!query || query === "@") return data;
1623
- if (query === "$") return data;
1624
- const dotMatch = query.match(/^([a-zA-Z_][a-zA-Z0-9_]*)(?:\.(.+))?$/);
1625
- if (dotMatch) {
1626
- const key = expectDefined(dotMatch[1]);
1627
- const rest = dotMatch[2];
1628
- const val = data?.[key];
1629
- if (rest === void 0) return val;
1630
- return jmespathSearch(val, rest);
1631
- }
1632
- const arrMatch = query.match(/^\[(\d+)\](?:\.(.+))?$/);
1633
- if (arrMatch) {
1634
- const idx = Number.parseInt(expectDefined(arrMatch[1]), 10);
1635
- const rest = arrMatch[2];
1636
- const arr = data;
1637
- const val = arr?.[idx];
1638
- if (rest === void 0) return val;
1639
- return jmespathSearch(val, rest);
1640
- }
1641
- if (query === "[*]") {
1642
- if (Array.isArray(data)) {
1643
- return data;
1644
- }
1645
- return data;
1646
- }
1647
- const multiMatch = query.match(/^([a-zA-Z_][a-zA-Z0-9_]*)\[\*\](?:\.(.+))?$/);
1648
- if (multiMatch) {
1649
- const key = expectDefined(multiMatch[1]);
1650
- const rest = multiMatch[2];
1651
- const arr = data?.[key];
1652
- if (!Array.isArray(arr)) return [];
1653
- if (rest === void 0) return arr;
1654
- return arr.map((item) => jmespathSearch(item, rest));
1655
- }
1656
- const filterMatch = query.match(/^\[\\?([a-zA-Z_][a-zA-Z0-9_]*)(==|!=|<|>|<=|>=)(`[^`]+`|'[^']*')\](?:\.(.+))?$/);
1657
- if (filterMatch) {
1658
- const field = expectDefined(filterMatch[1]);
1659
- const op = expectDefined(filterMatch[2]);
1660
- const rawVal = expectDefined(filterMatch[3]);
1661
- const rest = filterMatch[4];
1662
- const cmpVal = JSON.parse(rawVal.slice(1, -1));
1663
- const arr = data;
1664
- if (!Array.isArray(arr)) return [];
1665
- const filtered = arr.filter((item) => {
1666
- const itemVal = item[field];
1667
- switch (op) {
1668
- case "==":
1669
- return itemVal === cmpVal;
1670
- case "!=":
1671
- return itemVal !== cmpVal;
1672
- case ">":
1673
- return Number(itemVal) > Number(cmpVal);
1674
- case "<":
1675
- return Number(itemVal) < Number(cmpVal);
1676
- case ">=":
1677
- return Number(itemVal) >= Number(cmpVal);
1678
- case "<=":
1679
- return Number(itemVal) <= Number(cmpVal);
1680
- /* v8 ignore next -- op is constrained to the six operators by the filter regex; default is unreachable. */
1681
- default:
1682
- return true;
1683
- }
1684
- });
1685
- if (rest === void 0) return filtered;
1686
- return filtered.map((item) => jmespathSearch(item, rest));
1687
- }
1688
- const fnMatch = query.match(/^(length|keys|values|type)\(@\)$/);
1689
- if (fnMatch) {
1690
- const fn = expectDefined(fnMatch[1]);
1691
- switch (fn) {
1692
- case "length":
1693
- if (Array.isArray(data)) return data.length;
1694
- if (typeof data === "string") return data.length;
1695
- if (typeof data === "object" && data !== null) return Object.keys(data).length;
1696
- return 0;
1697
- case "keys":
1698
- if (typeof data === "object" && data !== null && !Array.isArray(data)) return Object.keys(data);
1699
- return [];
1700
- case "values":
1701
- if (typeof data === "object" && data !== null && !Array.isArray(data)) return Object.values(data);
1702
- return [];
1703
- case "type":
1704
- if (data === null) return "null";
1705
- if (Array.isArray(data)) return "array";
1706
- return typeof data;
1707
- /* v8 ignore next 2 -- fn is constrained to the four names by the function regex; default is unreachable. */
1708
- default:
1709
- return null;
1710
- }
1711
- }
1712
- return null;
1713
- }
1714
- function validateJsonSchema(data, schema) {
1715
- const errors = [];
1716
- function check(value, s, path2) {
1717
- if (s["type"]) {
1718
- const expectedType = s["type"];
1719
- const actualType = Array.isArray(value) ? "array" : value === null ? "null" : typeof value;
1720
- if (expectedType === "integer") {
1721
- if (!Number.isInteger(value)) errors.push(`${path2}: expected integer, got ${actualType}`);
1722
- } else if (expectedType !== actualType) {
1723
- errors.push(`${path2}: expected ${expectedType}, got ${actualType}`);
1724
- }
1725
- }
1726
- if (typeof value === "string" && s["format"] === "uri" && value) {
1727
- try {
1728
- new URL(value);
1729
- } catch {
1730
- errors.push(`${path2}: not a valid URI`);
1731
- }
1732
- }
1733
- if (typeof value === "string" && s["pattern"]) {
1734
- const re = new RegExp(s["pattern"]);
1735
- if (!re.test(value)) errors.push(`${path2}: does not match pattern ${s["pattern"]}`);
1736
- }
1737
- if (typeof value === "string" && s["minLength"] !== void 0 && value.length < s["minLength"]) {
1738
- errors.push(`${path2}: string too short (min ${s["minLength"]})`);
1739
- }
1740
- if (typeof value === "string" && s["maxLength"] !== void 0 && value.length > s["maxLength"]) {
1741
- errors.push(`${path2}: string too long (max ${s["maxLength"]})`);
1742
- }
1743
- if (typeof value === "number" && s["minimum"] !== void 0 && value < s["minimum"]) {
1744
- errors.push(`${path2}: below minimum ${s["minimum"]}`);
1745
- }
1746
- if (typeof value === "number" && s["maximum"] !== void 0 && value > s["maximum"]) {
1747
- errors.push(`${path2}: above maximum ${s["maximum"]}`);
1748
- }
1749
- if (Array.isArray(value) && s["items"] && Array.isArray(s["items"])) {
1750
- for (let i = 0; i < value.length; i++) {
1751
- check(value[i], s["items"], `${path2}[${i}]`);
1752
- }
1753
- }
1754
- if (typeof value === "object" && value !== null && !Array.isArray(value) && s["properties"]) {
1755
- const props = s["properties"];
1756
- for (const [k, propSchema] of Object.entries(props)) {
1757
- check(value[k], propSchema, `${path2}.${k}`);
1758
- }
1759
- }
1760
- }
1761
- check(data, schema, "$");
1762
- return { valid: errors.length === 0, errors };
1763
- }
1764
- function deepMerge(base, patch, conflictResolution = "prefer-patch") {
1765
- return deepMerge$1(base, patch, { conflictResolution });
1766
- }
1767
1161
  var plugin7 = {
1768
1162
  name: "json-path",
1769
- version: "0.1.0",
1770
- description: "JMESPath query, JSON Schema validation, transformation, and deep merge for JSON/YAML",
1163
+ version: "0.2.0",
1164
+ description: "Retired \u2014 capabilities merged into the built-in json tool",
1771
1165
  apiVersion: API_VERSION6,
1772
1166
  capabilities: { tools: true },
1773
- defaultConfig: {
1774
- strictValidation: false,
1775
- maxDepth: 50,
1776
- allowLargeFiles: false
1777
- },
1778
- configSchema: {
1779
- type: "object",
1780
- properties: {
1781
- strictValidation: { type: "boolean", default: false },
1782
- maxDepth: { type: "number", default: 50 },
1783
- allowLargeFiles: { type: "boolean", default: false }
1784
- }
1785
- },
1786
1167
  setup(api) {
1787
- api.tools.register({
1788
- name: "jmespath_query",
1789
- description: "Execute a JMESPath query on JSON or YAML data. Supports dot notation, array indexing, wildcards, filters, and functions.",
1790
- inputSchema: {
1791
- type: "object",
1792
- properties: {
1793
- data: { description: "JSON/YAML data to query (object or array)" },
1794
- query: { type: "string", description: "JMESPath query expression" }
1795
- },
1796
- required: ["data", "query"]
1797
- },
1798
- permission: "auto",
1799
- mutating: false,
1800
- async execute(input) {
1801
- const data = input["data"];
1802
- const query = input["query"];
1803
- try {
1804
- const result = jmespathSearch(data, query);
1805
- return {
1806
- ok: true,
1807
- query,
1808
- result,
1809
- resultType: result === null ? "null" : Array.isArray(result) ? "array" : typeof result
1810
- };
1811
- } catch (err) {
1812
- return { ok: false, error: String(err), query };
1813
- }
1814
- }
1815
- });
1816
- api.tools.register({
1817
- name: "json_validate",
1818
- description: "Validate JSON/YAML data against a JSON Schema. Reports all validation errors found.",
1819
- inputSchema: {
1820
- type: "object",
1821
- properties: {
1822
- data: { description: "JSON data to validate" },
1823
- schema: { description: "JSON Schema to validate against" }
1824
- },
1825
- required: ["data", "schema"]
1826
- },
1827
- permission: "auto",
1828
- mutating: false,
1829
- async execute(input) {
1830
- const data = input["data"];
1831
- const schema = input["schema"];
1832
- try {
1833
- const { valid, errors } = validateJsonSchema(data, schema);
1834
- return { ok: true, valid, errors, errorCount: errors.length };
1835
- } catch (err) {
1836
- return { ok: false, error: String(err) };
1837
- }
1838
- }
1839
- });
1840
- api.tools.register({
1841
- name: "json_transform",
1842
- description: "Apply a series of JMESPath transforms to data, passing the output of each as input to the next.",
1843
- inputSchema: {
1844
- type: "object",
1845
- properties: {
1846
- data: { description: "Initial JSON data" },
1847
- transforms: {
1848
- type: "array",
1849
- items: { type: "string" },
1850
- description: "Array of JMESPath query strings to apply in sequence"
1851
- }
1852
- },
1853
- required: ["data", "transforms"]
1854
- },
1855
- permission: "auto",
1856
- mutating: false,
1857
- async execute(input) {
1858
- const data = input["data"];
1859
- const transforms = input["transforms"];
1860
- try {
1861
- let current = data;
1862
- const steps = [];
1863
- for (const t of transforms) {
1864
- current = jmespathSearch(current, t);
1865
- steps.push({ transform: t, result: current });
1866
- }
1867
- return { ok: true, finalResult: current, steps };
1868
- } catch (err) {
1869
- return { ok: false, error: String(err) };
1870
- }
1871
- }
1872
- });
1873
- api.tools.register({
1874
- name: "json_merge",
1875
- description: "Deep merge two JSON objects. Use conflictResolution to decide which value wins on collision.",
1876
- inputSchema: {
1877
- type: "object",
1878
- properties: {
1879
- base: { description: "Base JSON object" },
1880
- patch: { description: "Patch JSON object to merge in" },
1881
- conflictResolution: {
1882
- type: "string",
1883
- enum: ["prefer-base", "prefer-patch"],
1884
- default: "prefer-patch"
1885
- }
1886
- },
1887
- required: ["base", "patch"]
1888
- },
1889
- permission: "auto",
1890
- mutating: false,
1891
- async execute(input) {
1892
- const base = input["base"];
1893
- const patch = input["patch"];
1894
- const conflictResolution = input["conflictResolution"] ?? "prefer-patch";
1895
- try {
1896
- const result = deepMerge(base, patch, conflictResolution);
1897
- return { ok: true, result };
1898
- } catch (err) {
1899
- return { ok: false, error: String(err) };
1900
- }
1901
- }
1902
- });
1903
- api.log.info("json-path plugin loaded", { version: "0.1.0" });
1168
+ api.log.info("json-path plugin retired \u2014 use the built-in json tool with action: query|validate|transform|merge");
1904
1169
  }
1905
1170
  };
1906
1171
  var json_path_default = plugin7;
@@ -2026,6 +1291,7 @@ var plugin8 = {
2026
1291
  required: ["name", "intervalMs", "action"]
2027
1292
  },
2028
1293
  permission: "confirm",
1294
+ category: "Session",
2029
1295
  mutating: false,
2030
1296
  capabilities: [COORDINATION_CRON_CAPABILITY],
2031
1297
  async execute(input) {
@@ -2215,17 +1481,18 @@ var plugin9 = {
2215
1481
  description: "Variables to substitute into the template",
2216
1482
  additionalProperties: { type: "string" }
2217
1483
  },
2218
- outputPath: { type: "string", description: "Optional path to write the expanded result" },
1484
+ output_path: { type: "string", description: "Optional path to write the expanded result" },
2219
1485
  raw: { type: "boolean", default: false, description: "Disable HTML auto-escaping" }
2220
1486
  },
2221
1487
  required: ["template", "variables"]
2222
1488
  },
2223
1489
  permission: "auto",
1490
+ category: "Project",
2224
1491
  mutating: true,
2225
1492
  async execute(input) {
2226
1493
  const template = input["template"];
2227
1494
  const variables = input["variables"];
2228
- const outputPath = input["outputPath"];
1495
+ const output_path = input["output_path"];
2229
1496
  const raw = input["raw"] ?? false;
2230
1497
  if (!template || typeof template !== "string") {
2231
1498
  return { ok: false, error: "template is required and must be a string" };
@@ -2239,17 +1506,17 @@ var plugin9 = {
2239
1506
  } catch (err) {
2240
1507
  return { ok: false, error: String(err) };
2241
1508
  }
2242
- if (outputPath) {
2243
- if (isAbsolute(outputPath) || outputPath.includes("..")) {
2244
- return { ok: false, error: 'outputPath must be a relative path without ".." components' };
1509
+ if (output_path) {
1510
+ if (isAbsolute(output_path) || output_path.includes("..")) {
1511
+ return { ok: false, error: 'output_path must be a relative path without ".." components' };
2245
1512
  }
2246
1513
  const { writeFileSync: writeFileSync2 } = await import('fs');
2247
- writeFileSync2(outputPath, result, "utf-8");
1514
+ writeFileSync2(output_path, result, "utf-8");
2248
1515
  return {
2249
1516
  ok: true,
2250
- outputPath,
1517
+ output_path,
2251
1518
  contentLength: result.length,
2252
- message: `Wrote ${result.length} characters to ${outputPath}`
1519
+ message: `Wrote ${result.length} characters to ${output_path}`
2253
1520
  };
2254
1521
  }
2255
1522
  return {
@@ -2266,26 +1533,26 @@ var plugin9 = {
2266
1533
  inputSchema: {
2267
1534
  type: "object",
2268
1535
  properties: {
2269
- templatePath: { type: "string", description: "Path to the template file" },
1536
+ template_path: { type: "string", description: "Path to the template file" },
2270
1537
  variables: {
2271
1538
  type: "object",
2272
1539
  description: "Variables to substitute",
2273
1540
  additionalProperties: { type: "string" }
2274
1541
  },
2275
- outputPath: { type: "string", description: "Optional path to write the rendered result" },
1542
+ output_path: { type: "string", description: "Optional path to write the rendered result" },
2276
1543
  raw: { type: "boolean", default: false }
2277
1544
  },
2278
- required: ["templatePath", "variables"]
1545
+ required: ["template_path", "variables"]
2279
1546
  },
2280
1547
  permission: "auto",
2281
1548
  mutating: true,
2282
1549
  async execute(input) {
2283
- const templatePath = input["templatePath"];
1550
+ const template_path = input["template_path"];
2284
1551
  const variables = input["variables"];
2285
- const outputPath = input["outputPath"];
1552
+ const output_path = input["output_path"];
2286
1553
  const raw = input["raw"] ?? false;
2287
- if (!templatePath || typeof templatePath !== "string") {
2288
- return { ok: false, error: "templatePath is required and must be a string" };
1554
+ if (!template_path || typeof template_path !== "string") {
1555
+ return { ok: false, error: "template_path is required and must be a string" };
2289
1556
  }
2290
1557
  if (!variables || typeof variables !== "object") {
2291
1558
  return { ok: false, error: "variables is required and must be an object" };
@@ -2293,7 +1560,7 @@ var plugin9 = {
2293
1560
  let content;
2294
1561
  try {
2295
1562
  const { readFileSync: readFileSync2 } = await import('fs');
2296
- content = readFileSync2(templatePath, "utf-8");
1563
+ content = readFileSync2(template_path, "utf-8");
2297
1564
  } catch (err) {
2298
1565
  return { ok: false, error: `Could not read template file: ${err}` };
2299
1566
  }
@@ -2303,22 +1570,22 @@ var plugin9 = {
2303
1570
  } catch (err) {
2304
1571
  return { ok: false, error: `Template rendering failed: ${err}` };
2305
1572
  }
2306
- if (outputPath) {
2307
- if (isAbsolute(outputPath) || outputPath.includes("..")) {
2308
- return { ok: false, error: 'outputPath must be a relative path without ".." components' };
1573
+ if (output_path) {
1574
+ if (isAbsolute(output_path) || output_path.includes("..")) {
1575
+ return { ok: false, error: 'output_path must be a relative path without ".." components' };
2309
1576
  }
2310
1577
  const { writeFileSync: writeFileSync2 } = await import('fs');
2311
- writeFileSync2(outputPath, result, "utf-8");
1578
+ writeFileSync2(output_path, result, "utf-8");
2312
1579
  return {
2313
1580
  ok: true,
2314
- templatePath,
2315
- outputPath,
2316
- message: `Rendered and wrote ${result.length} chars to ${outputPath}`
1581
+ template_path,
1582
+ output_path,
1583
+ message: `Rendered and wrote ${result.length} chars to ${output_path}`
2317
1584
  };
2318
1585
  }
2319
1586
  return {
2320
1587
  ok: true,
2321
- templatePath,
1588
+ template_path,
2322
1589
  result,
2323
1590
  contentLength: result.length
2324
1591
  };
@@ -2606,7 +1873,7 @@ var plugin10 = {
2606
1873
  if (dryRun) {
2607
1874
  return {
2608
1875
  ok: true,
2609
- dryRun: true,
1876
+ dry_run: true,
2610
1877
  currentVersion,
2611
1878
  suggestedBump: bumpPart,
2612
1879
  newVersion,
@@ -2676,7 +1943,7 @@ var plugin10 = {
2676
1943
  type: "object",
2677
1944
  properties: {
2678
1945
  cwd: { type: "string", description: "Working directory (defaults to project root)" },
2679
- dryRun: { type: "boolean", default: false },
1946
+ dry_run: { type: "boolean", default: false },
2680
1947
  part: { type: "string", enum: ["major", "minor", "patch", "auto"], default: defaultPart, description: "Version part to bump. Omitted \u2192 the configured default (/settings semver-part, factory default: patch). Use auto to infer from commits." }
2681
1948
  }
2682
1949
  },
@@ -2684,7 +1951,7 @@ var plugin10 = {
2684
1951
  mutating: true,
2685
1952
  async execute(input) {
2686
1953
  const cwd = input["cwd"];
2687
- const dryRun = input["dryRun"] ?? false;
1954
+ const dryRun = input["dry_run"] ?? false;
2688
1955
  const part = input["part"] ?? defaultPart;
2689
1956
  return performBump(part, dryRun, cwd);
2690
1957
  }