@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/auto-doc.d.ts +3 -2
- package/dist/auto-doc.js +6 -44
- package/dist/cost-tracker.js +1 -0
- package/dist/cron.js +1 -0
- package/dist/file-watcher.js +15 -14
- package/dist/git-autocommit.d.ts +6 -3
- package/dist/git-autocommit.js +7 -137
- package/dist/index.js +111 -844
- package/dist/json-path.d.ts +9 -6
- package/dist/json-path.js +3 -280
- package/dist/semver-bump.js +3 -3
- package/dist/shell-check.d.ts +5 -2
- package/dist/shell-check.js +42 -72
- package/dist/template-engine.js +25 -24
- package/dist/web-search.d.ts +14 -0
- package/dist/web-search.js +4 -274
- package/package.json +2 -2
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
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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["
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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(
|
|
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(
|
|
641
|
+
api.metrics.histogram("issues_per_file", issues.length / Math.max(checkFiles.length, 1));
|
|
783
642
|
return {
|
|
784
643
|
ok: true,
|
|
785
|
-
|
|
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.
|
|
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
|
|
883
|
+
var watch_idCounter = 0;
|
|
1083
884
|
function nextId() {
|
|
1084
|
-
return `watch_${++
|
|
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
|
-
|
|
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",
|
|
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",
|
|
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
|
-
|
|
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
|
-
|
|
1074
|
+
watch_id: { type: "string", description: "Watch ID returned by watch_start" }
|
|
1273
1075
|
},
|
|
1274
|
-
required: ["
|
|
1076
|
+
required: ["watch_id"]
|
|
1275
1077
|
},
|
|
1276
1078
|
permission: "auto",
|
|
1277
1079
|
mutating: false,
|
|
1278
1080
|
async execute(input) {
|
|
1279
|
-
const
|
|
1280
|
-
const handle = watches.get(
|
|
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: ${
|
|
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(
|
|
1092
|
+
watches.delete(watch_id);
|
|
1291
1093
|
api.metrics.gauge("active_watches", watches.size);
|
|
1292
1094
|
return {
|
|
1293
1095
|
ok: true,
|
|
1294
|
-
|
|
1295
|
-
message: `Stopped watch ${
|
|
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(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"').replace(/'/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
|
-
|
|
1462
|
-
html = html.replace(/<[^>]+>/g, "");
|
|
1463
|
-
html = html.replace(/&/g, "&");
|
|
1464
|
-
html = html.replace(/</g, "<");
|
|
1465
|
-
html = html.replace(/>/g, ">");
|
|
1466
|
-
html = html.replace(/"/g, '"');
|
|
1467
|
-
html = html.replace(/'/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.
|
|
1487
|
-
description: "
|
|
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
|
|
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
|
-
|
|
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.
|
|
1770
|
-
description: "
|
|
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.
|
|
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
|
-
|
|
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
|
|
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 (
|
|
2243
|
-
if (isAbsolute(
|
|
2244
|
-
return { ok: false, error: '
|
|
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(
|
|
1514
|
+
writeFileSync2(output_path, result, "utf-8");
|
|
2248
1515
|
return {
|
|
2249
1516
|
ok: true,
|
|
2250
|
-
|
|
1517
|
+
output_path,
|
|
2251
1518
|
contentLength: result.length,
|
|
2252
|
-
message: `Wrote ${result.length} characters to ${
|
|
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
|
-
|
|
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
|
-
|
|
1542
|
+
output_path: { type: "string", description: "Optional path to write the rendered result" },
|
|
2276
1543
|
raw: { type: "boolean", default: false }
|
|
2277
1544
|
},
|
|
2278
|
-
required: ["
|
|
1545
|
+
required: ["template_path", "variables"]
|
|
2279
1546
|
},
|
|
2280
1547
|
permission: "auto",
|
|
2281
1548
|
mutating: true,
|
|
2282
1549
|
async execute(input) {
|
|
2283
|
-
const
|
|
1550
|
+
const template_path = input["template_path"];
|
|
2284
1551
|
const variables = input["variables"];
|
|
2285
|
-
const
|
|
1552
|
+
const output_path = input["output_path"];
|
|
2286
1553
|
const raw = input["raw"] ?? false;
|
|
2287
|
-
if (!
|
|
2288
|
-
return { ok: false, error: "
|
|
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(
|
|
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 (
|
|
2307
|
-
if (isAbsolute(
|
|
2308
|
-
return { ok: false, error: '
|
|
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(
|
|
1578
|
+
writeFileSync2(output_path, result, "utf-8");
|
|
2312
1579
|
return {
|
|
2313
1580
|
ok: true,
|
|
2314
|
-
|
|
2315
|
-
|
|
2316
|
-
message: `Rendered and wrote ${result.length} chars to ${
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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["
|
|
1954
|
+
const dryRun = input["dry_run"] ?? false;
|
|
2688
1955
|
const part = input["part"] ?? defaultPart;
|
|
2689
1956
|
return performBump(part, dryRun, cwd);
|
|
2690
1957
|
}
|