agent-gov-core 0.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +92 -0
- package/dist/action.d.ts +31 -0
- package/dist/action.d.ts.map +1 -0
- package/dist/action.js +79 -0
- package/dist/action.js.map +1 -0
- package/dist/finding.d.ts +115 -0
- package/dist/finding.d.ts.map +1 -0
- package/dist/finding.js +177 -0
- package/dist/finding.js.map +1 -0
- package/dist/index.d.ts +13 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -0
- package/dist/jsonc.d.ts +20 -0
- package/dist/jsonc.d.ts.map +1 -0
- package/dist/jsonc.js +117 -0
- package/dist/jsonc.js.map +1 -0
- package/dist/locators.d.ts +29 -0
- package/dist/locators.d.ts.map +1 -0
- package/dist/locators.js +127 -0
- package/dist/locators.js.map +1 -0
- package/dist/mcp.d.ts +32 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +120 -0
- package/dist/mcp.js.map +1 -0
- package/dist/shell.d.ts +33 -0
- package/dist/shell.d.ts.map +1 -0
- package/dist/shell.js +200 -0
- package/dist/shell.js.map +1 -0
- package/dist/test-utils.d.ts +65 -0
- package/dist/test-utils.d.ts.map +1 -0
- package/dist/test-utils.js +90 -0
- package/dist/test-utils.js.map +1 -0
- package/dist/toml.d.ts +28 -0
- package/dist/toml.d.ts.map +1 -0
- package/dist/toml.js +0 -0
- package/dist/toml.js.map +1 -0
- package/package.json +51 -0
- package/schemas/finding.schema.json +46 -0
package/dist/jsonc.js
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Strip `//` line comments, `/* ... *\/` block comments, and trailing commas from JSONC,
|
|
4
|
+
* preserving byte offsets (replacement is space-filled, newlines preserved) so downstream
|
|
5
|
+
* line locators still match the original `text`.
|
|
6
|
+
*/
|
|
7
|
+
export function stripJsonComments(input) {
|
|
8
|
+
const len = input.length;
|
|
9
|
+
const out = new Array(len);
|
|
10
|
+
let i = 0;
|
|
11
|
+
let inString = null;
|
|
12
|
+
let escape = false;
|
|
13
|
+
while (i < len) {
|
|
14
|
+
const ch = input[i];
|
|
15
|
+
if (inString) {
|
|
16
|
+
out[i] = ch;
|
|
17
|
+
if (escape) {
|
|
18
|
+
escape = false;
|
|
19
|
+
}
|
|
20
|
+
else if (ch === '\\') {
|
|
21
|
+
escape = true;
|
|
22
|
+
}
|
|
23
|
+
else if (ch === inString) {
|
|
24
|
+
inString = null;
|
|
25
|
+
}
|
|
26
|
+
i++;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (ch === '"') {
|
|
30
|
+
inString = '"';
|
|
31
|
+
out[i] = ch;
|
|
32
|
+
i++;
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
if (ch === '/' && i + 1 < len) {
|
|
36
|
+
const next = input[i + 1];
|
|
37
|
+
if (next === '/') {
|
|
38
|
+
// line comment until newline (exclusive)
|
|
39
|
+
let j = i;
|
|
40
|
+
while (j < len && input[j] !== '\n' && input[j] !== '\r') {
|
|
41
|
+
out[j] = ' ';
|
|
42
|
+
j++;
|
|
43
|
+
}
|
|
44
|
+
i = j;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (next === '*') {
|
|
48
|
+
let j = i;
|
|
49
|
+
// replace until end of block comment (inclusive of */)
|
|
50
|
+
const end = input.indexOf('*/', i + 2);
|
|
51
|
+
const stop = end === -1 ? len : end + 2;
|
|
52
|
+
while (j < stop) {
|
|
53
|
+
const c = input[j];
|
|
54
|
+
out[j] = c === '\n' || c === '\r' ? c : ' ';
|
|
55
|
+
j++;
|
|
56
|
+
}
|
|
57
|
+
i = j;
|
|
58
|
+
continue;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
out[i] = ch;
|
|
62
|
+
i++;
|
|
63
|
+
}
|
|
64
|
+
let result = out.join('');
|
|
65
|
+
// Strip trailing commas: `,` followed by optional whitespace then `}` or `]`.
|
|
66
|
+
// Only outside strings — but at this point we've reconstructed the source character-by-character,
|
|
67
|
+
// and the only commas we want to elide are structural ones. A safe pass: walk again with string-state.
|
|
68
|
+
result = stripTrailingCommas(result);
|
|
69
|
+
return result;
|
|
70
|
+
}
|
|
71
|
+
function stripTrailingCommas(input) {
|
|
72
|
+
const len = input.length;
|
|
73
|
+
const out = input.split('');
|
|
74
|
+
let inString = null;
|
|
75
|
+
let escape = false;
|
|
76
|
+
for (let i = 0; i < len; i++) {
|
|
77
|
+
const ch = out[i];
|
|
78
|
+
if (inString) {
|
|
79
|
+
if (escape)
|
|
80
|
+
escape = false;
|
|
81
|
+
else if (ch === '\\')
|
|
82
|
+
escape = true;
|
|
83
|
+
else if (ch === inString)
|
|
84
|
+
inString = null;
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (ch === '"') {
|
|
88
|
+
inString = '"';
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (ch === ',') {
|
|
92
|
+
let j = i + 1;
|
|
93
|
+
while (j < len && /\s/.test(out[j]))
|
|
94
|
+
j++;
|
|
95
|
+
if (j < len && (out[j] === '}' || out[j] === ']')) {
|
|
96
|
+
out[i] = ' ';
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return out.join('');
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Read a JSONC file and return both the parsed value and the original text.
|
|
104
|
+
* The original text is preserved exactly so line locators can operate on it.
|
|
105
|
+
*/
|
|
106
|
+
export function readJsonObjectWithSource(path) {
|
|
107
|
+
const text = readFileSync(path, 'utf8');
|
|
108
|
+
try {
|
|
109
|
+
const stripped = stripJsonComments(text);
|
|
110
|
+
const json = JSON.parse(stripped);
|
|
111
|
+
return { json, text };
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
return { json: undefined, text, parseError: err };
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
//# sourceMappingURL=jsonc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"jsonc.js","sourceRoot":"","sources":["../src/jsonc.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line locators operate on the *original* source text — JSONC strip-comments
|
|
3
|
+
* is position-preserving so offsets line up.
|
|
4
|
+
*
|
|
5
|
+
* All returned line numbers are 1-based. `0` is reserved for "not found"; callers
|
|
6
|
+
* generally treat that as "fall back to file-level annotation".
|
|
7
|
+
*/
|
|
8
|
+
export interface ByteRange {
|
|
9
|
+
/** Inclusive start offset. */
|
|
10
|
+
start: number;
|
|
11
|
+
/** Exclusive end offset. */
|
|
12
|
+
end: number;
|
|
13
|
+
}
|
|
14
|
+
/** 1-based line number for the first occurrence of `"key"` followed by `:`. */
|
|
15
|
+
export declare function lineOfJsonKey(text: string, key: string, scope?: ByteRange): number;
|
|
16
|
+
/**
|
|
17
|
+
* 1-based line number for the first JSON string value equal to `value`.
|
|
18
|
+
* If `scope` is supplied (a byte range), only matches inside that range count —
|
|
19
|
+
* this is the fix for the multi-server-ambiguity bug.
|
|
20
|
+
*/
|
|
21
|
+
export declare function lineOfJsonStringValue(text: string, value: string, scope?: ByteRange): number;
|
|
22
|
+
/**
|
|
23
|
+
* 1-based line number for a TOML key. Supports dotted keys (`a.b.c`) — the
|
|
24
|
+
* locator points to the line where the *leaf* key is defined, scanning forward
|
|
25
|
+
* from the line of the matching `[a.b]` table header (or the start of the file
|
|
26
|
+
* for top-level keys). Bare and quoted leaf keys are both handled.
|
|
27
|
+
*/
|
|
28
|
+
export declare function lineOfTomlKey(text: string, dottedKey: string): number;
|
|
29
|
+
//# sourceMappingURL=locators.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locators.d.ts","sourceRoot":"","sources":["../src/locators.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,SAAS;IACxB,8BAA8B;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,GAAG,EAAE,MAAM,CAAC;CACb;AAED,+EAA+E;AAC/E,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAGlF;AAED;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,SAAS,GAAG,MAAM,CAG5F;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAwCrE"}
|
package/dist/locators.js
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Line locators operate on the *original* source text — JSONC strip-comments
|
|
3
|
+
* is position-preserving so offsets line up.
|
|
4
|
+
*
|
|
5
|
+
* All returned line numbers are 1-based. `0` is reserved for "not found"; callers
|
|
6
|
+
* generally treat that as "fall back to file-level annotation".
|
|
7
|
+
*/
|
|
8
|
+
/** 1-based line number for the first occurrence of `"key"` followed by `:`. */
|
|
9
|
+
export function lineOfJsonKey(text, key, scope) {
|
|
10
|
+
const needle = `"${escapeForRegex(key)}"\\s*:`;
|
|
11
|
+
return findLineByRegex(text, new RegExp(needle), scope);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 1-based line number for the first JSON string value equal to `value`.
|
|
15
|
+
* If `scope` is supplied (a byte range), only matches inside that range count —
|
|
16
|
+
* this is the fix for the multi-server-ambiguity bug.
|
|
17
|
+
*/
|
|
18
|
+
export function lineOfJsonStringValue(text, value, scope) {
|
|
19
|
+
const needle = `"${escapeForRegex(value)}"`;
|
|
20
|
+
return findLineByRegex(text, new RegExp(needle), scope);
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 1-based line number for a TOML key. Supports dotted keys (`a.b.c`) — the
|
|
24
|
+
* locator points to the line where the *leaf* key is defined, scanning forward
|
|
25
|
+
* from the line of the matching `[a.b]` table header (or the start of the file
|
|
26
|
+
* for top-level keys). Bare and quoted leaf keys are both handled.
|
|
27
|
+
*/
|
|
28
|
+
export function lineOfTomlKey(text, dottedKey) {
|
|
29
|
+
const parts = splitTomlDottedKey(dottedKey);
|
|
30
|
+
if (parts.length === 0)
|
|
31
|
+
return 0;
|
|
32
|
+
const leaf = parts[parts.length - 1];
|
|
33
|
+
const prefix = parts.slice(0, -1);
|
|
34
|
+
const lines = text.split(/\r?\n/);
|
|
35
|
+
// Find header range we're inside of.
|
|
36
|
+
let inTargetTable = prefix.length === 0;
|
|
37
|
+
let currentTable = [];
|
|
38
|
+
const targetHeader = prefix.join('.');
|
|
39
|
+
for (let i = 0; i < lines.length; i++) {
|
|
40
|
+
const raw = lines[i];
|
|
41
|
+
const trimmed = raw.trim();
|
|
42
|
+
const headerMatch = /^\[\[?\s*([^\]]+?)\s*\]\]?\s*(#.*)?$/.exec(trimmed);
|
|
43
|
+
if (headerMatch) {
|
|
44
|
+
currentTable = splitTomlDottedKey(headerMatch[1]);
|
|
45
|
+
inTargetTable = currentTable.join('.') === targetHeader;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!inTargetTable)
|
|
49
|
+
continue;
|
|
50
|
+
if (trimmed === '' || trimmed.startsWith('#'))
|
|
51
|
+
continue;
|
|
52
|
+
// Match leaf key at start of line: bare, "quoted", or 'literal'
|
|
53
|
+
const leafPattern = new RegExp(`^\\s*(?:${escapeForRegex(leaf)}|"${escapeForRegex(leaf)}"|'${escapeForRegex(leaf)}')\\s*(?:\\.|=)`);
|
|
54
|
+
if (leafPattern.test(raw))
|
|
55
|
+
return i + 1;
|
|
56
|
+
// Also: dotted key like `prefix.leaf = ...` defined at top-level
|
|
57
|
+
if (prefix.length > 0 && currentTable.length === 0) {
|
|
58
|
+
const dottedPattern = new RegExp(`^\\s*${escapeForRegex(dottedKey)}\\s*=`);
|
|
59
|
+
if (dottedPattern.test(raw))
|
|
60
|
+
return i + 1;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return 0;
|
|
64
|
+
}
|
|
65
|
+
function findLineByRegex(text, regex, scope) {
|
|
66
|
+
const haystack = scope ? text.slice(scope.start, scope.end) : text;
|
|
67
|
+
const m = regex.exec(haystack);
|
|
68
|
+
if (!m)
|
|
69
|
+
return 0;
|
|
70
|
+
const offset = (scope ? scope.start : 0) + m.index;
|
|
71
|
+
return lineOfOffset(text, offset);
|
|
72
|
+
}
|
|
73
|
+
function lineOfOffset(text, offset) {
|
|
74
|
+
let line = 1;
|
|
75
|
+
for (let i = 0; i < offset && i < text.length; i++) {
|
|
76
|
+
if (text[i] === '\n')
|
|
77
|
+
line++;
|
|
78
|
+
}
|
|
79
|
+
return line;
|
|
80
|
+
}
|
|
81
|
+
function escapeForRegex(s) {
|
|
82
|
+
return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
83
|
+
}
|
|
84
|
+
/** Split a TOML dotted key, honoring "quoted" and 'literal' parts. */
|
|
85
|
+
function splitTomlDottedKey(input) {
|
|
86
|
+
const parts = [];
|
|
87
|
+
let i = 0;
|
|
88
|
+
const len = input.length;
|
|
89
|
+
while (i < len) {
|
|
90
|
+
while (i < len && (input[i] === ' ' || input[i] === '\t'))
|
|
91
|
+
i++;
|
|
92
|
+
if (i >= len)
|
|
93
|
+
break;
|
|
94
|
+
const c = input[i];
|
|
95
|
+
if (c === '"') {
|
|
96
|
+
i++;
|
|
97
|
+
const start = i;
|
|
98
|
+
while (i < len && input[i] !== '"') {
|
|
99
|
+
if (input[i] === '\\')
|
|
100
|
+
i++;
|
|
101
|
+
i++;
|
|
102
|
+
}
|
|
103
|
+
parts.push(input.slice(start, i));
|
|
104
|
+
i++;
|
|
105
|
+
}
|
|
106
|
+
else if (c === "'") {
|
|
107
|
+
i++;
|
|
108
|
+
const start = i;
|
|
109
|
+
while (i < len && input[i] !== "'")
|
|
110
|
+
i++;
|
|
111
|
+
parts.push(input.slice(start, i));
|
|
112
|
+
i++;
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
const start = i;
|
|
116
|
+
while (i < len && input[i] !== '.' && input[i] !== ' ' && input[i] !== '\t')
|
|
117
|
+
i++;
|
|
118
|
+
parts.push(input.slice(start, i));
|
|
119
|
+
}
|
|
120
|
+
while (i < len && (input[i] === ' ' || input[i] === '\t'))
|
|
121
|
+
i++;
|
|
122
|
+
if (i < len && input[i] === '.')
|
|
123
|
+
i++;
|
|
124
|
+
}
|
|
125
|
+
return parts;
|
|
126
|
+
}
|
|
127
|
+
//# sourceMappingURL=locators.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"locators.js","sourceRoot":"","sources":["../src/locators.ts"],"names":[],"mappings":"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"}
|
package/dist/mcp.d.ts
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export interface McpCommandSpec {
|
|
2
|
+
command?: string;
|
|
3
|
+
args?: readonly string[];
|
|
4
|
+
/** Some configs use a URL (SSE/remote MCP). */
|
|
5
|
+
url?: string;
|
|
6
|
+
env?: Readonly<Record<string, string>>;
|
|
7
|
+
cwd?: string;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Returns a canonical identity string for an MCP server command.
|
|
11
|
+
*
|
|
12
|
+
* Goals:
|
|
13
|
+
* - Two specs that differ only in cosmetic ways (flag reordering, `.cmd`/`.exe`
|
|
14
|
+
* on Windows, equivalent env var ordering) hash the same.
|
|
15
|
+
* - Specs that differ in anything *load-bearing* (the executable, the URL, the
|
|
16
|
+
* cwd, any env value, any non-neutral arg) hash differently.
|
|
17
|
+
*
|
|
18
|
+
* Non-goals:
|
|
19
|
+
* - Understanding tool semantics. Two truly-different `--flag value` pairs hash
|
|
20
|
+
* differently even if the tool would treat them equivalently.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* normalizeMcpCommand({ command: 'npx.cmd', args: ['-y', 'mcp-foo', '--token', 'abc'] });
|
|
24
|
+
* normalizeMcpCommand({ command: 'npx', args: ['mcp-foo', '--token', 'abc'] });
|
|
25
|
+
* // → both produce the same canonical string
|
|
26
|
+
*
|
|
27
|
+
* @example
|
|
28
|
+
* normalizeMcpCommand({ url: 'https://example.com/mcp/' });
|
|
29
|
+
* // → 'url=https://example.com/mcp\nargs='
|
|
30
|
+
*/
|
|
31
|
+
export declare function normalizeMcpCommand(spec: McpCommandSpec): string;
|
|
32
|
+
//# sourceMappingURL=mcp.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.d.ts","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,cAAc;IAC7B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,CAAC;IACzB,+CAA+C;IAC/C,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvC,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,cAAc,GAAG,MAAM,CA0BhE"}
|
package/dist/mcp.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Returns a canonical identity string for an MCP server command.
|
|
3
|
+
*
|
|
4
|
+
* Goals:
|
|
5
|
+
* - Two specs that differ only in cosmetic ways (flag reordering, `.cmd`/`.exe`
|
|
6
|
+
* on Windows, equivalent env var ordering) hash the same.
|
|
7
|
+
* - Specs that differ in anything *load-bearing* (the executable, the URL, the
|
|
8
|
+
* cwd, any env value, any non-neutral arg) hash differently.
|
|
9
|
+
*
|
|
10
|
+
* Non-goals:
|
|
11
|
+
* - Understanding tool semantics. Two truly-different `--flag value` pairs hash
|
|
12
|
+
* differently even if the tool would treat them equivalently.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* normalizeMcpCommand({ command: 'npx.cmd', args: ['-y', 'mcp-foo', '--token', 'abc'] });
|
|
16
|
+
* normalizeMcpCommand({ command: 'npx', args: ['mcp-foo', '--token', 'abc'] });
|
|
17
|
+
* // → both produce the same canonical string
|
|
18
|
+
*
|
|
19
|
+
* @example
|
|
20
|
+
* normalizeMcpCommand({ url: 'https://example.com/mcp/' });
|
|
21
|
+
* // → 'url=https://example.com/mcp\nargs='
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeMcpCommand(spec) {
|
|
24
|
+
const parts = [];
|
|
25
|
+
if (spec.url) {
|
|
26
|
+
parts.push(`url=${spec.url.trim().replace(/\/$/, '')}`);
|
|
27
|
+
}
|
|
28
|
+
if (spec.command) {
|
|
29
|
+
parts.push(`cmd=${normalizeExecutable(spec.command)}`);
|
|
30
|
+
}
|
|
31
|
+
const args = spec.args ?? [];
|
|
32
|
+
parts.push(`args=${canonicalizeArgs(args).join(' ')}`);
|
|
33
|
+
if (spec.cwd) {
|
|
34
|
+
parts.push(`cwd=${normalizePath(spec.cwd)}`);
|
|
35
|
+
}
|
|
36
|
+
if (spec.env) {
|
|
37
|
+
const env = Object.entries(spec.env)
|
|
38
|
+
.map(([k, v]) => `${k}=${v}`)
|
|
39
|
+
.sort();
|
|
40
|
+
parts.push(`env=${env.join('|')}`);
|
|
41
|
+
}
|
|
42
|
+
return parts.join('\n');
|
|
43
|
+
}
|
|
44
|
+
/** Strip `.cmd`/`.exe`/`.bat`/`.ps1` suffix and lowercase on Windows-style paths. */
|
|
45
|
+
function normalizeExecutable(cmd) {
|
|
46
|
+
const trimmed = cmd.trim();
|
|
47
|
+
const base = trimmed.replace(/\\/g, '/');
|
|
48
|
+
const withoutSuffix = base.replace(/\.(cmd|exe|bat|ps1)$/i, '');
|
|
49
|
+
return withoutSuffix;
|
|
50
|
+
}
|
|
51
|
+
function normalizePath(p) {
|
|
52
|
+
return p.trim().replace(/\\/g, '/').replace(/\/+$/, '');
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Boolean flags that don't change *what* runs, just confirmation/verbosity.
|
|
56
|
+
* Dropped before canonicalization so e.g. `npx -y foo@1.2.3` and `npx foo@1.2.3`
|
|
57
|
+
* normalize identically. Keep this list conservative — only flags whose presence
|
|
58
|
+
* vs. absence is provably neutral across the runners that show up in MCP configs
|
|
59
|
+
* (npx, uvx, pipx, node).
|
|
60
|
+
*/
|
|
61
|
+
const NEUTRAL_BOOLEAN_FLAGS = new Set(['-y', '--yes']);
|
|
62
|
+
/**
|
|
63
|
+
* Sort *neutral* flag/value pairs so reordering doesn't change identity, but
|
|
64
|
+
* preserve the order of positional arguments (which are usually load-bearing —
|
|
65
|
+
* e.g. `npx <package> <subcommand>`).
|
|
66
|
+
*
|
|
67
|
+
* Heuristic: an argument starting with `-` is a flag. A flag followed by a
|
|
68
|
+
* non-flag is treated as `--flag value` and the pair is sorted together. We
|
|
69
|
+
* keep them in two buckets: positional (order-preserved) and flag-pairs (sorted).
|
|
70
|
+
*
|
|
71
|
+
* Neutral boolean flags (see NEUTRAL_BOOLEAN_FLAGS) are dropped entirely so they
|
|
72
|
+
* never absorb a trailing positional as a fake `--flag value` pair.
|
|
73
|
+
*/
|
|
74
|
+
function canonicalizeArgs(args) {
|
|
75
|
+
const filtered = args.filter((a) => !NEUTRAL_BOOLEAN_FLAGS.has(a));
|
|
76
|
+
const positional = [];
|
|
77
|
+
const flagPairs = [];
|
|
78
|
+
let sawFlag = false;
|
|
79
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
80
|
+
const a = filtered[i];
|
|
81
|
+
if (a.startsWith('-')) {
|
|
82
|
+
sawFlag = true;
|
|
83
|
+
// `--key=value`
|
|
84
|
+
const eq = a.indexOf('=');
|
|
85
|
+
if (eq !== -1) {
|
|
86
|
+
flagPairs.push([a.slice(0, eq), a.slice(eq + 1)]);
|
|
87
|
+
continue;
|
|
88
|
+
}
|
|
89
|
+
const next = filtered[i + 1];
|
|
90
|
+
if (next !== undefined && !next.startsWith('-')) {
|
|
91
|
+
flagPairs.push([a, next]);
|
|
92
|
+
i++;
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
flagPairs.push([a, null]);
|
|
96
|
+
}
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
if (sawFlag) {
|
|
100
|
+
// After flags have started, an unattached positional gets a deterministic position
|
|
101
|
+
// — push it into the sorted-pair bucket as a value-only entry keyed by itself.
|
|
102
|
+
flagPairs.push([`__pos__${a}`, null]);
|
|
103
|
+
}
|
|
104
|
+
else {
|
|
105
|
+
positional.push(a);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
flagPairs.sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0));
|
|
109
|
+
const out = [...positional];
|
|
110
|
+
for (const [k, v] of flagPairs) {
|
|
111
|
+
if (k.startsWith('__pos__'))
|
|
112
|
+
out.push(k.slice('__pos__'.length));
|
|
113
|
+
else if (v === null)
|
|
114
|
+
out.push(k);
|
|
115
|
+
else
|
|
116
|
+
out.push(`${k}=${v}`);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
//# sourceMappingURL=mcp.js.map
|
package/dist/mcp.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mcp.js","sourceRoot":"","sources":["../src/mcp.ts"],"names":[],"mappings":"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"}
|
package/dist/shell.d.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quote-aware split of a shell command into subcommands on `;`, `|`, `&&`, `||`.
|
|
3
|
+
* Does NOT execute the shell. Designed for static detection: SessionTrail's
|
|
4
|
+
* shell detector and future per-line CapabilityEcho rules call this to identify
|
|
5
|
+
* suspicious subcommands hidden behind chaining and basic obfuscation.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* tokenizeShell('echo hi && curl https://x.com/install.sh | bash');
|
|
9
|
+
* // → ['echo hi', 'curl https://x.com/install.sh', 'bash']
|
|
10
|
+
*
|
|
11
|
+
* tokenizeShell('echo "; not a separator"');
|
|
12
|
+
* // → ['echo "; not a separator"']
|
|
13
|
+
*/
|
|
14
|
+
export declare function tokenizeShell(command: string): string[];
|
|
15
|
+
/**
|
|
16
|
+
* Returns the resolved command verb for a subcommand string. Strips wrapping
|
|
17
|
+
* quotes, escape backslashes, and the inert-double-quote obfuscation
|
|
18
|
+
* (`c""url` → `curl`, `c\\url` → `curl`).
|
|
19
|
+
*
|
|
20
|
+
* Returns an empty string if the input has no recognizable command head.
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* getCommandHead('FOO=bar sudo curl -fsSL https://x.com');
|
|
24
|
+
* // → 'curl'
|
|
25
|
+
*
|
|
26
|
+
* getCommandHead('c""url -X POST');
|
|
27
|
+
* // → 'curl'
|
|
28
|
+
*
|
|
29
|
+
* getCommandHead('"/usr/bin/env" python3 -c "..."');
|
|
30
|
+
* // → '/usr/bin/env'
|
|
31
|
+
*/
|
|
32
|
+
export declare function getCommandHead(subcommand: string): string;
|
|
33
|
+
//# sourceMappingURL=shell.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"shell.d.ts","sourceRoot":"","sources":["../src/shell.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,wBAAgB,aAAa,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAuFvD;AAOD;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAiBzD"}
|