@paretools/shared 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,25 @@
1
+ # @paretools/shared
2
+
3
+ Shared utilities for Pare MCP servers.
4
+
5
+ ## Exports
6
+
7
+ - **`run(command, args, options)`** — Executes a CLI command via `execFile` (no shell injection) and returns `{ stdout, stderr, exitCode }`
8
+ - **`dualOutput(data, formatter)`** — Returns both `structuredContent` (typed JSON) and `content` (human-readable text) for MCP tool responses
9
+ - **`stripAnsi(text)`** — Removes ANSI escape codes from CLI output
10
+
11
+ ## Usage
12
+
13
+ This package is used internally by all `@paretools/*` server packages. You generally don't need to install it directly unless you're building a custom Pare server.
14
+
15
+ ```typescript
16
+ import { run, dualOutput, stripAnsi } from "@paretools/shared";
17
+ ```
18
+
19
+ ## Links
20
+
21
+ - [Pare monorepo](https://github.com/Dave-London/pare)
22
+
23
+ ## License
24
+
25
+ [MIT](https://github.com/Dave-London/pare/blob/main/LICENSE)
package/dist/index.d.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export { dualOutput } from "./output.js";
2
2
  export { run, type RunResult, type RunOptions } from "./runner.js";
3
3
  export { stripAnsi } from "./ansi.js";
4
+ export { assertNoFlagInjection, assertAllowedCommand } from "./validation.js";
4
5
  export type { ToolOutput } from "./types.js";
5
6
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,GAAG,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  export { dualOutput } from "./output.js";
2
2
  export { run } from "./runner.js";
3
3
  export { stripAnsi } from "./ansi.js";
4
+ export { assertNoFlagInjection, assertAllowedCommand } from "./validation.js";
4
5
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,GAAG,EAAmC,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,GAAG,EAAmC,MAAM,aAAa,CAAC;AACnE,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
package/dist/runner.d.ts CHANGED
@@ -13,6 +13,9 @@ export interface RunResult {
13
13
  * Uses execFile (not exec) to avoid shell injection. On Windows, shell is
14
14
  * enabled so that .cmd/.bat wrappers (like npx) can be executed — args are
15
15
  * still passed as an array so they remain properly escaped.
16
+ *
17
+ * Throws on system-level errors (command not found, permission denied).
18
+ * Normal non-zero exit codes are returned in the result, not thrown.
16
19
  */
17
20
  export declare function run(cmd: string, args: string[], opts?: RunOptions): Promise<RunResult>;
18
21
  //# sourceMappingURL=runner.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAqBtF"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,UAAU;IACzB,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,GAAG,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9B;AAED,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAkDtF"}
package/dist/runner.js CHANGED
@@ -5,9 +5,12 @@ import { stripAnsi } from "./ansi.js";
5
5
  * Uses execFile (not exec) to avoid shell injection. On Windows, shell is
6
6
  * enabled so that .cmd/.bat wrappers (like npx) can be executed — args are
7
7
  * still passed as an array so they remain properly escaped.
8
+ *
9
+ * Throws on system-level errors (command not found, permission denied).
10
+ * Normal non-zero exit codes are returned in the result, not thrown.
8
11
  */
9
12
  export function run(cmd, args, opts) {
10
- return new Promise((resolve) => {
13
+ return new Promise((resolve, reject) => {
11
14
  execFile(cmd, args, {
12
15
  cwd: opts?.cwd,
13
16
  timeout: opts?.timeout ?? 30_000,
@@ -15,8 +18,26 @@ export function run(cmd, args, opts) {
15
18
  maxBuffer: 10 * 1024 * 1024, // 10 MB
16
19
  shell: process.platform === "win32",
17
20
  }, (error, stdout, stderr) => {
21
+ if (error) {
22
+ const errno = error;
23
+ // Unix: direct ENOENT from execFile (no shell wrapping)
24
+ if (errno.code === "ENOENT") {
25
+ reject(new Error(`Command not found: "${cmd}". Ensure it is installed and available in your PATH.`));
26
+ return;
27
+ }
28
+ if (errno.code === "EACCES" || errno.code === "EPERM") {
29
+ reject(new Error(`Permission denied executing "${cmd}": ${errno.message}`));
30
+ return;
31
+ }
32
+ // Windows: cmd.exe masks ENOENT — detect via stderr message
33
+ const cleanStderr = stripAnsi(stderr);
34
+ if (cleanStderr.includes("is not recognized")) {
35
+ reject(new Error(`Command not found: "${cmd}". Ensure it is installed and available in your PATH.`));
36
+ return;
37
+ }
38
+ }
18
39
  resolve({
19
- exitCode: error && "code" in error ? error.code : error ? 1 : 0,
40
+ exitCode: error ? (typeof error.code === "number" ? error.code : 1) : 0,
20
41
  stdout: stripAnsi(stdout),
21
42
  stderr: stripAnsi(stderr),
22
43
  });
@@ -1 +1 @@
1
- {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AActC;;;;;GAKG;AACH,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,IAAc,EAAE,IAAiB;IAChE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,QAAQ,CACN,GAAG,EACH,IAAI,EACJ;YACE,GAAG,EAAE,IAAI,EAAE,GAAG;YACd,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,MAAM;YAChC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;YAC5D,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ;YACrC,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;SACpC,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,OAAO,CAAC;gBACN,QAAQ,EAAE,KAAK,IAAI,MAAM,IAAI,KAAK,CAAC,CAAC,CAAE,KAAK,CAAC,IAAe,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBAC3E,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;gBACzB,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;aAC1B,CAAC,CAAC;QACL,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;AAC9C,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AActC;;;;;;;;GAQG;AACH,MAAM,UAAU,GAAG,CAAC,GAAW,EAAE,IAAc,EAAE,IAAiB;IAChE,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,QAAQ,CACN,GAAG,EACH,IAAI,EACJ;YACE,GAAG,EAAE,IAAI,EAAE,GAAG;YACd,OAAO,EAAE,IAAI,EAAE,OAAO,IAAI,MAAM;YAChC,GAAG,EAAE,IAAI,EAAE,GAAG,CAAC,CAAC,CAAC,EAAE,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC,CAAC,CAAC,SAAS;YAC5D,SAAS,EAAE,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,QAAQ;YACrC,KAAK,EAAE,OAAO,CAAC,QAAQ,KAAK,OAAO;SACpC,EACD,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,EAAE;YACxB,IAAI,KAAK,EAAE,CAAC;gBACV,MAAM,KAAK,GAAG,KAA8B,CAAC;gBAE7C,wDAAwD;gBACxD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;oBAC5B,MAAM,CACJ,IAAI,KAAK,CACP,uBAAuB,GAAG,uDAAuD,CAClF,CACF,CAAC;oBACF,OAAO;gBACT,CAAC;gBACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;oBACtD,MAAM,CAAC,IAAI,KAAK,CAAC,gCAAgC,GAAG,MAAM,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBAC5E,OAAO;gBACT,CAAC;gBAED,4DAA4D;gBAC5D,MAAM,WAAW,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;gBACtC,IAAI,WAAW,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;oBAC9C,MAAM,CACJ,IAAI,KAAK,CACP,uBAAuB,GAAG,uDAAuD,CAClF,CACF,CAAC;oBACF,OAAO;gBACT,CAAC;YACH,CAAC;YAED,OAAO,CAAC;gBACN,QAAQ,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACvE,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;gBACzB,MAAM,EAAE,SAAS,CAAC,MAAM,CAAC;aAC1B,CAAC,CAAC;QACL,CAAC,CACF,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,8 @@
1
+ /**
2
+ * Validates that a string argument is safe to pass as a positional argument to a CLI tool.
3
+ * Prevents flag injection attacks (e.g., passing "--output=/etc/passwd" as a ref name).
4
+ * See: CVE-2025-68144, CVE-2025-68145
5
+ */
6
+ export declare function assertNoFlagInjection(value: string, paramName: string): void;
7
+ export declare function assertAllowedCommand(command: string): void;
8
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,wBAAgB,qBAAqB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CAM5E;AAiCD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAe1D"}
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Validates that a string argument is safe to pass as a positional argument to a CLI tool.
3
+ * Prevents flag injection attacks (e.g., passing "--output=/etc/passwd" as a ref name).
4
+ * See: CVE-2025-68144, CVE-2025-68145
5
+ */
6
+ export function assertNoFlagInjection(value, paramName) {
7
+ if (value.startsWith("-")) {
8
+ throw new Error(`Invalid ${paramName}: "${value}". Values must not start with "-" to prevent argument injection.`);
9
+ }
10
+ }
11
+ /**
12
+ * Allowlist of known safe build commands.
13
+ * Prevents arbitrary command execution via the build tool's command parameter.
14
+ */
15
+ const ALLOWED_BUILD_COMMANDS = new Set([
16
+ "npm",
17
+ "npx",
18
+ "pnpm",
19
+ "yarn",
20
+ "bun",
21
+ "bunx",
22
+ "make",
23
+ "cmake",
24
+ "gradle",
25
+ "gradlew",
26
+ "mvn",
27
+ "ant",
28
+ "cargo",
29
+ "go",
30
+ "dotnet",
31
+ "msbuild",
32
+ "tsc",
33
+ "esbuild",
34
+ "vite",
35
+ "webpack",
36
+ "rollup",
37
+ "turbo",
38
+ "nx",
39
+ "bazel",
40
+ ]);
41
+ export function assertAllowedCommand(command) {
42
+ // Extract the base command name (handle paths like /usr/bin/npm or C:\npm.cmd)
43
+ const base = command
44
+ .replace(/\\/g, "/")
45
+ .split("/")
46
+ .pop()
47
+ ?.replace(/\.(cmd|exe|bat|sh)$/i, "") ?? "";
48
+ if (!ALLOWED_BUILD_COMMANDS.has(base)) {
49
+ throw new Error(`Command "${command}" is not in the allowed build commands list. ` +
50
+ `Allowed: ${[...ALLOWED_BUILD_COMMANDS].sort().join(", ")}`);
51
+ }
52
+ }
53
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,UAAU,qBAAqB,CAAC,KAAa,EAAE,SAAiB;IACpE,IAAI,KAAK,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC1B,MAAM,IAAI,KAAK,CACb,WAAW,SAAS,MAAM,KAAK,kEAAkE,CAClG,CAAC;IACJ,CAAC;AACH,CAAC;AAED;;;GAGG;AACH,MAAM,sBAAsB,GAAG,IAAI,GAAG,CAAC;IACrC,KAAK;IACL,KAAK;IACL,MAAM;IACN,MAAM;IACN,KAAK;IACL,MAAM;IACN,MAAM;IACN,OAAO;IACP,QAAQ;IACR,SAAS;IACT,KAAK;IACL,KAAK;IACL,OAAO;IACP,IAAI;IACJ,QAAQ;IACR,SAAS;IACT,KAAK;IACL,SAAS;IACT,MAAM;IACN,SAAS;IACT,QAAQ;IACR,OAAO;IACP,IAAI;IACJ,OAAO;CACR,CAAC,CAAC;AAEH,MAAM,UAAU,oBAAoB,CAAC,OAAe;IAClD,+EAA+E;IAC/E,MAAM,IAAI,GACR,OAAO;SACJ,OAAO,CAAC,KAAK,EAAE,GAAG,CAAC;SACnB,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,EAAE;QACN,EAAE,OAAO,CAAC,sBAAsB,EAAE,EAAE,CAAC,IAAI,EAAE,CAAC;IAEhD,IAAI,CAAC,sBAAsB,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QACtC,MAAM,IAAI,KAAK,CACb,YAAY,OAAO,+CAA+C;YAChE,YAAY,CAAC,GAAG,sBAAsB,CAAC,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAC9D,CAAC;IACJ,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,18 @@
1
1
  {
2
2
  "name": "@paretools/shared",
3
- "version": "0.2.0",
4
- "description": "Shared utilities for pare MCP servers",
3
+ "version": "0.3.0",
4
+ "description": "Shared utilities for Pare MCP servers",
5
5
  "license": "MIT",
6
+ "keywords": [
7
+ "mcp",
8
+ "mcp-server",
9
+ "model-context-protocol",
10
+ "structured-output"
11
+ ],
12
+ "homepage": "https://github.com/Dave-London/pare/tree/main/packages/shared",
13
+ "engines": {
14
+ "node": ">=18"
15
+ },
6
16
  "type": "module",
7
17
  "exports": {
8
18
  ".": {