@paretools/shared 0.5.0 → 0.7.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 CHANGED
@@ -1,9 +1,9 @@
1
1
  # @paretools/shared
2
2
 
3
3
  [![npm](https://img.shields.io/npm/v/@paretools/shared.svg)](https://www.npmjs.com/package/@paretools/shared)
4
- [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Dave-London/pare/blob/main/LICENSE)
4
+ [![License: MIT](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/Dave-London/Pare/blob/main/LICENSE)
5
5
 
6
- Shared utilities for [Pare](https://github.com/Dave-London/pare) MCP servers.
6
+ Shared utilities for [Pare](https://github.com/Dave-London/Pare) MCP servers.
7
7
 
8
8
  ## Exports
9
9
 
@@ -21,8 +21,8 @@ import { run, dualOutput, stripAnsi } from "@paretools/shared";
21
21
 
22
22
  ## Links
23
23
 
24
- - [Pare monorepo](https://github.com/Dave-London/pare)
24
+ - [Pare monorepo](https://github.com/Dave-London/Pare)
25
25
 
26
26
  ## License
27
27
 
28
- [MIT](https://github.com/Dave-London/pare/blob/main/LICENSE)
28
+ [MIT](https://github.com/Dave-London/Pare/blob/main/LICENSE)
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
- export { dualOutput } from "./output.js";
2
- export { run, type RunResult, type RunOptions } from "./runner.js";
1
+ export { dualOutput, estimateTokens, compactDualOutput } from "./output.js";
2
+ export { run, escapeCmdArg, type RunResult, type RunOptions } from "./runner.js";
3
3
  export { stripAnsi } from "./ansi.js";
4
4
  export { assertNoFlagInjection, assertAllowedCommand } from "./validation.js";
5
+ export { INPUT_LIMITS } from "./limits.js";
6
+ export { sanitizeErrorOutput } from "./sanitize.js";
7
+ export { shouldRegisterTool } from "./tool-filter.js";
5
8
  export type { ToolOutput } from "./types.js";
6
9
  //# 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,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,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,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC;AACtD,YAAY,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC"}
package/dist/index.js CHANGED
@@ -1,5 +1,8 @@
1
- export { dualOutput } from "./output.js";
2
- export { run } from "./runner.js";
1
+ export { dualOutput, estimateTokens, compactDualOutput } from "./output.js";
2
+ export { run, escapeCmdArg } from "./runner.js";
3
3
  export { stripAnsi } from "./ansi.js";
4
4
  export { assertNoFlagInjection, assertAllowedCommand } from "./validation.js";
5
+ export { INPUT_LIMITS } from "./limits.js";
6
+ export { sanitizeErrorOutput } from "./sanitize.js";
7
+ export { shouldRegisterTool } from "./tool-filter.js";
5
8
  //# 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;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC5E,OAAO,EAAE,GAAG,EAAE,YAAY,EAAmC,MAAM,aAAa,CAAC;AACjF,OAAO,EAAE,SAAS,EAAE,MAAM,WAAW,CAAC;AACtC,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAC9E,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAC3C,OAAO,EAAE,mBAAmB,EAAE,MAAM,eAAe,CAAC;AACpD,OAAO,EAAE,kBAAkB,EAAE,MAAM,kBAAkB,CAAC"}
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared input-validation limits for all Pare MCP servers.
3
+ *
4
+ * These provide defense-in-depth against DoS via extremely long inputs.
5
+ * Applied to Zod input schemas with `.max()` — output schemas are NOT
6
+ * constrained because their size is determined by tool output, not user input.
7
+ */
8
+ export declare const INPUT_LIMITS: {
9
+ /** Maximum length for any general string parameter (64 KB). */
10
+ readonly STRING_MAX: 65536;
11
+ /** Maximum items in any array parameter. */
12
+ readonly ARRAY_MAX: 1000;
13
+ /** Maximum length for file system paths. */
14
+ readonly PATH_MAX: 4096;
15
+ /** Maximum length for commit messages (generous to allow long messages). */
16
+ readonly MESSAGE_MAX: 72000;
17
+ /** Maximum length for short identifiers (branch names, package names, etc.). */
18
+ readonly SHORT_STRING_MAX: 255;
19
+ };
20
+ //# sourceMappingURL=limits.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.d.ts","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,eAAO,MAAM,YAAY;IACvB,+DAA+D;;IAG/D,4CAA4C;;IAG5C,4CAA4C;;IAG5C,4EAA4E;;IAG5E,gFAAgF;;CAExE,CAAC"}
package/dist/limits.js ADDED
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Shared input-validation limits for all Pare MCP servers.
3
+ *
4
+ * These provide defense-in-depth against DoS via extremely long inputs.
5
+ * Applied to Zod input schemas with `.max()` — output schemas are NOT
6
+ * constrained because their size is determined by tool output, not user input.
7
+ */
8
+ export const INPUT_LIMITS = {
9
+ /** Maximum length for any general string parameter (64 KB). */
10
+ STRING_MAX: 65_536,
11
+ /** Maximum items in any array parameter. */
12
+ ARRAY_MAX: 1_000,
13
+ /** Maximum length for file system paths. */
14
+ PATH_MAX: 4_096,
15
+ /** Maximum length for commit messages (generous to allow long messages). */
16
+ MESSAGE_MAX: 72_000,
17
+ /** Maximum length for short identifiers (branch names, package names, etc.). */
18
+ SHORT_STRING_MAX: 255,
19
+ };
20
+ //# sourceMappingURL=limits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"limits.js","sourceRoot":"","sources":["../src/limits.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG;IAC1B,+DAA+D;IAC/D,UAAU,EAAE,MAAM;IAElB,4CAA4C;IAC5C,SAAS,EAAE,KAAK;IAEhB,4CAA4C;IAC5C,QAAQ,EAAE,KAAK;IAEf,4EAA4E;IAC5E,WAAW,EAAE,MAAM;IAEnB,gFAAgF;IAChF,gBAAgB,EAAE,GAAG;CACb,CAAC"}
package/dist/output.d.ts CHANGED
@@ -9,4 +9,24 @@ import type { ToolOutput } from "./types.js";
9
9
  * @param humanFormat - A function that formats `data` as human-readable text.
10
10
  */
11
11
  export declare function dualOutput<T>(data: T, humanFormat: (d: T) => string): ToolOutput<T>;
12
+ /**
13
+ * Estimates the token count of a string using the ~4 chars/token heuristic.
14
+ */
15
+ export declare function estimateTokens(text: string): number;
16
+ /**
17
+ * Creates a dual-output response with automatic compact mode.
18
+ *
19
+ * Compares the token cost of the full structured JSON against the raw CLI stdout.
20
+ * When the structured output would use more tokens than raw, applies a compact
21
+ * projection to reduce the schema. Setting `forceFullSchema` to true always
22
+ * returns the full (non-compact) data.
23
+ *
24
+ * @param data - The full structured data parsed from CLI output.
25
+ * @param rawStdout - The ANSI-stripped stdout from the CLI command.
26
+ * @param humanFormat - Formatter for full data (used when not compacting).
27
+ * @param compactMap - Projects full data into a compact shape.
28
+ * @param compactFormat - Formatter for compact data.
29
+ * @param forceFullSchema - When true, skip auto-detection and return full data.
30
+ */
31
+ export declare function compactDualOutput<T, C>(data: T, rawStdout: string, humanFormat: (d: T) => string, compactMap: (d: T) => C, compactFormat: (d: C) => string, forceFullSchema: boolean): ToolOutput<T | C>;
12
32
  //# sourceMappingURL=output.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAKnF"}
1
+ {"version":3,"file":"output.d.ts","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;;;;GAQG;AACH,wBAAgB,UAAU,CAAC,CAAC,EAAE,IAAI,EAAE,CAAC,EAAE,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,GAAG,UAAU,CAAC,CAAC,CAAC,CAKnF;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,CAAC,EACpC,IAAI,EAAE,CAAC,EACP,SAAS,EAAE,MAAM,EACjB,WAAW,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,EAC7B,UAAU,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,EACvB,aAAa,EAAE,CAAC,CAAC,EAAE,CAAC,KAAK,MAAM,EAC/B,eAAe,EAAE,OAAO,GACvB,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAcnB"}
package/dist/output.js CHANGED
@@ -13,4 +13,37 @@ export function dualOutput(data, humanFormat) {
13
13
  structuredContent: data,
14
14
  };
15
15
  }
16
+ /**
17
+ * Estimates the token count of a string using the ~4 chars/token heuristic.
18
+ */
19
+ export function estimateTokens(text) {
20
+ return Math.ceil(text.length / 4);
21
+ }
22
+ /**
23
+ * Creates a dual-output response with automatic compact mode.
24
+ *
25
+ * Compares the token cost of the full structured JSON against the raw CLI stdout.
26
+ * When the structured output would use more tokens than raw, applies a compact
27
+ * projection to reduce the schema. Setting `forceFullSchema` to true always
28
+ * returns the full (non-compact) data.
29
+ *
30
+ * @param data - The full structured data parsed from CLI output.
31
+ * @param rawStdout - The ANSI-stripped stdout from the CLI command.
32
+ * @param humanFormat - Formatter for full data (used when not compacting).
33
+ * @param compactMap - Projects full data into a compact shape.
34
+ * @param compactFormat - Formatter for compact data.
35
+ * @param forceFullSchema - When true, skip auto-detection and return full data.
36
+ */
37
+ export function compactDualOutput(data, rawStdout, humanFormat, compactMap, compactFormat, forceFullSchema) {
38
+ if (forceFullSchema) {
39
+ return dualOutput(data, humanFormat);
40
+ }
41
+ const structuredTokens = estimateTokens(JSON.stringify(data));
42
+ const rawTokens = estimateTokens(rawStdout);
43
+ if (structuredTokens >= rawTokens) {
44
+ const compact = compactMap(data);
45
+ return dualOutput(compact, compactFormat);
46
+ }
47
+ return dualOutput(data, humanFormat);
48
+ }
16
49
  //# sourceMappingURL=output.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAI,IAAO,EAAE,WAA6B;IAClE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC"}
1
+ {"version":3,"file":"output.js","sourceRoot":"","sources":["../src/output.ts"],"names":[],"mappings":"AAEA;;;;;;;;GAQG;AACH,MAAM,UAAU,UAAU,CAAI,IAAO,EAAE,WAA6B;IAClE,OAAO;QACL,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,iBAAiB,EAAE,IAAI;KACxB,CAAC;AACJ,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,cAAc,CAAC,IAAY;IACzC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACpC,CAAC;AAED;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,iBAAiB,CAC/B,IAAO,EACP,SAAiB,EACjB,WAA6B,EAC7B,UAAuB,EACvB,aAA+B,EAC/B,eAAwB;IAExB,IAAI,eAAe,EAAE,CAAC;QACpB,OAAO,UAAU,CAAC,IAAI,EAAE,WAAW,CAAsB,CAAC;IAC5D,CAAC;IAED,MAAM,gBAAgB,GAAG,cAAc,CAAC,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC;IAC9D,MAAM,SAAS,GAAG,cAAc,CAAC,SAAS,CAAC,CAAC;IAE5C,IAAI,gBAAgB,IAAI,SAAS,EAAE,CAAC;QAClC,MAAM,OAAO,GAAG,UAAU,CAAC,IAAI,CAAC,CAAC;QACjC,OAAO,UAAU,CAAC,OAAO,EAAE,aAAa,CAAsB,CAAC;IACjE,CAAC;IAED,OAAO,UAAU,CAAC,IAAI,EAAE,WAAW,CAAsB,CAAC;AAC5D,CAAC"}
package/dist/runner.d.ts CHANGED
@@ -3,6 +3,10 @@ export interface RunOptions {
3
3
  cwd?: string;
4
4
  timeout?: number;
5
5
  env?: Record<string, string>;
6
+ /** Data to write to the child process's stdin. Useful for piping content
7
+ * (e.g., `git commit --file -`) instead of passing it as a command arg,
8
+ * which avoids cmd.exe argument-length and newline limitations on Windows. */
9
+ stdin?: string;
6
10
  }
7
11
  /** Result of a command execution, containing the exit code and ANSI-stripped stdout/stderr. */
8
12
  export interface RunResult {
@@ -10,6 +14,26 @@ export interface RunResult {
10
14
  stdout: string;
11
15
  stderr: string;
12
16
  }
17
+ /**
18
+ * Escapes a single argument for safe use with cmd.exe on Windows.
19
+ *
20
+ * When `shell: true` is used with `execFile` on Windows, Node.js joins the
21
+ * command and args with spaces to build a cmd.exe command line. It does NOT
22
+ * automatically quote individual arguments, so args containing spaces would
23
+ * be split into multiple tokens by cmd.exe.
24
+ *
25
+ * Strategy:
26
+ * - Args with spaces, tabs, or double quotes are wrapped in double quotes.
27
+ * Inside double quotes cmd.exe treats `&`, `|`, `<`, `>`, `^` as literal,
28
+ * so only `%` needs escaping (`%%`). Internal `"` chars are doubled (`""`).
29
+ * - Args without spaces are left unquoted, with cmd.exe metacharacters
30
+ * escaped via the caret (`^`) prefix.
31
+ * - `%` → `%%` is applied unconditionally (active in both quoted/unquoted).
32
+ *
33
+ * Parentheses `(` `)` are NOT escaped because inside double-quoted strings
34
+ * they are literal, and outside quotes they rarely appear in tool args.
35
+ */
36
+ export declare function escapeCmdArg(arg: string): string;
13
37
  /**
14
38
  * Executes a command and returns cleaned output with ANSI codes stripped.
15
39
  * Uses execFile (not exec) to avoid shell injection. On Windows, shell is
@@ -1 +1 @@
1
- {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAGA,uGAAuG;AACvG,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,+FAA+F;AAC/F,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"}
1
+ {"version":3,"file":"runner.d.ts","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"AAIA,uGAAuG;AACvG,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;IAC7B;;mFAE+E;IAC/E,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,+FAA+F;AAC/F,MAAM,WAAW,SAAS;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CA8BhD;AAED;;;;;;;;GAQG;AACH,wBAAgB,GAAG,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,IAAI,CAAC,EAAE,UAAU,GAAG,OAAO,CAAC,SAAS,CAAC,CAyEtF"}
package/dist/runner.js CHANGED
@@ -1,5 +1,52 @@
1
1
  import { execFile } from "node:child_process";
2
2
  import { stripAnsi } from "./ansi.js";
3
+ import { sanitizeErrorOutput } from "./sanitize.js";
4
+ /**
5
+ * Escapes a single argument for safe use with cmd.exe on Windows.
6
+ *
7
+ * When `shell: true` is used with `execFile` on Windows, Node.js joins the
8
+ * command and args with spaces to build a cmd.exe command line. It does NOT
9
+ * automatically quote individual arguments, so args containing spaces would
10
+ * be split into multiple tokens by cmd.exe.
11
+ *
12
+ * Strategy:
13
+ * - Args with spaces, tabs, or double quotes are wrapped in double quotes.
14
+ * Inside double quotes cmd.exe treats `&`, `|`, `<`, `>`, `^` as literal,
15
+ * so only `%` needs escaping (`%%`). Internal `"` chars are doubled (`""`).
16
+ * - Args without spaces are left unquoted, with cmd.exe metacharacters
17
+ * escaped via the caret (`^`) prefix.
18
+ * - `%` → `%%` is applied unconditionally (active in both quoted/unquoted).
19
+ *
20
+ * Parentheses `(` `)` are NOT escaped because inside double-quoted strings
21
+ * they are literal, and outside quotes they rarely appear in tool args.
22
+ */
23
+ export function escapeCmdArg(arg) {
24
+ // Step 1: Escape % → %% to prevent %VAR% expansion (works quoted & unquoted)
25
+ let escaped = arg.replace(/%/g, "%%");
26
+ // Step 2: If the arg contains spaces, tabs, or double quotes, wrap it in
27
+ // double quotes so cmd.exe keeps it as a single token.
28
+ if (/[ \t"]/.test(arg)) {
29
+ // Replace newlines with spaces — cmd.exe treats \n as a command-line
30
+ // terminator even inside double quotes, so literal newlines cannot be
31
+ // passed via command args. Tools that need newline-preserving input
32
+ // should use RunOptions.stdin instead (e.g., git commit --file -).
33
+ escaped = escaped.replace(/\r?\n/g, " ");
34
+ // Escape internal double quotes by doubling them
35
+ escaped = escaped.replace(/"/g, '""');
36
+ return `"${escaped}"`;
37
+ }
38
+ // Step 3: For unquoted args, caret-escape cmd.exe metacharacters.
39
+ // The caret itself must be escaped first so that carets we insert
40
+ // are not themselves re-escaped.
41
+ escaped = escaped.replace(/\^/g, "^^");
42
+ escaped = escaped.replace(/&/g, "^&");
43
+ escaped = escaped.replace(/\|/g, "^|");
44
+ escaped = escaped.replace(/</g, "^<");
45
+ escaped = escaped.replace(/>/g, "^>");
46
+ // Step 4: Escape ! to prevent delayed expansion (!VAR!)
47
+ escaped = escaped.replace(/!/g, "^!");
48
+ return escaped;
49
+ }
3
50
  /**
4
51
  * Executes a command and returns cleaned output with ANSI codes stripped.
5
52
  * Uses execFile (not exec) to avoid shell injection. On Windows, shell is
@@ -11,9 +58,13 @@ import { stripAnsi } from "./ansi.js";
11
58
  */
12
59
  export function run(cmd, args, opts) {
13
60
  return new Promise((resolve, reject) => {
14
- execFile(cmd, args, {
61
+ // On Windows with shell mode, escape cmd.exe metacharacters to prevent
62
+ // environment variable expansion and command injection via special chars.
63
+ // See escapeCmdArg() for details on what is escaped and why.
64
+ const safeArgs = process.platform === "win32" ? args.map(escapeCmdArg) : args;
65
+ const child = execFile(cmd, safeArgs, {
15
66
  cwd: opts?.cwd,
16
- timeout: opts?.timeout ?? 30_000,
67
+ timeout: opts?.timeout ?? 60_000,
17
68
  env: opts?.env ? { ...process.env, ...opts.env } : undefined,
18
69
  maxBuffer: 10 * 1024 * 1024, // 10 MB
19
70
  shell: process.platform === "win32",
@@ -29,6 +80,12 @@ export function run(cmd, args, opts) {
29
80
  reject(new Error(`Permission denied executing "${cmd}": ${errno.message}`));
30
81
  return;
31
82
  }
83
+ // Timeout: execFile killed the child after the configured timeout.
84
+ // Surface this clearly instead of silently returning exitCode 1.
85
+ if (error.killed && error.signal) {
86
+ reject(new Error(`Command "${cmd}" timed out after ${opts?.timeout ?? 60_000}ms and was killed (${error.signal}).`));
87
+ return;
88
+ }
32
89
  // Windows: cmd.exe masks ENOENT — detect via stderr message
33
90
  const cleanStderr = stripAnsi(stderr);
34
91
  if (cleanStderr.includes("is not recognized")) {
@@ -39,9 +96,15 @@ export function run(cmd, args, opts) {
39
96
  resolve({
40
97
  exitCode: error ? (typeof error.code === "number" ? error.code : 1) : 0,
41
98
  stdout: stripAnsi(stdout),
42
- stderr: stripAnsi(stderr),
99
+ stderr: sanitizeErrorOutput(stripAnsi(stderr)),
43
100
  });
44
101
  });
102
+ // Pipe stdin data if provided, then close the stream so the child
103
+ // process knows input is complete (e.g., `git commit --file -`).
104
+ if (opts?.stdin != null) {
105
+ child.stdin?.write(opts.stdin);
106
+ child.stdin?.end();
107
+ }
45
108
  });
46
109
  }
47
110
  //# sourceMappingURL=runner.js.map
@@ -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;AAgBtC;;;;;;;;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"}
1
+ {"version":3,"file":"runner.js","sourceRoot":"","sources":["../src/runner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Sanitizes error output by replacing sensitive filesystem paths with
3
+ * home-relative equivalents. This prevents leaking usernames and absolute
4
+ * home directory paths in error messages returned to MCP clients.
5
+ *
6
+ * Replacements:
7
+ * /home/<user>/... → ~/...
8
+ * /Users/<user>/... → ~/...
9
+ * /root/... → ~/...
10
+ * C:\Users\<user>\... → ~\...
11
+ */
12
+ export declare function sanitizeErrorOutput(text: string): string;
13
+ //# sourceMappingURL=sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.d.ts","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAcxD"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Sanitizes error output by replacing sensitive filesystem paths with
3
+ * home-relative equivalents. This prevents leaking usernames and absolute
4
+ * home directory paths in error messages returned to MCP clients.
5
+ *
6
+ * Replacements:
7
+ * /home/<user>/... → ~/...
8
+ * /Users/<user>/... → ~/...
9
+ * /root/... → ~/...
10
+ * C:\Users\<user>\... → ~\...
11
+ */
12
+ export function sanitizeErrorOutput(text) {
13
+ // Unix: /home/<username>/rest → ~/rest
14
+ let result = text.replace(/\/home\/[^/\s]+\//g, "~/");
15
+ // macOS: /Users/<username>/rest → ~/rest
16
+ result = result.replace(/\/Users\/[^/\s]+\//g, "~/");
17
+ // Unix: /root/rest → ~/rest
18
+ result = result.replace(/\/root\//g, "~/");
19
+ // Windows: C:\Users\<username>\rest → ~\rest (with escaped or literal backslashes)
20
+ result = result.replace(/[A-Z]:\\Users\\[^\\:\s]+\\/gi, "~\\");
21
+ return result;
22
+ }
23
+ //# sourceMappingURL=sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitize.js","sourceRoot":"","sources":["../src/sanitize.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB,CAAC,IAAY;IAC9C,uCAAuC;IACvC,IAAI,MAAM,GAAG,IAAI,CAAC,OAAO,CAAC,oBAAoB,EAAE,IAAI,CAAC,CAAC;IAEtD,yCAAyC;IACzC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,qBAAqB,EAAE,IAAI,CAAC,CAAC;IAErD,4BAA4B;IAC5B,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,WAAW,EAAE,IAAI,CAAC,CAAC;IAE3C,mFAAmF;IACnF,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,8BAA8B,EAAE,KAAK,CAAC,CAAC;IAE/D,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Granular tool selection — allows users to enable/disable individual tools
3
+ * via environment variables.
4
+ *
5
+ * Environment variable precedence:
6
+ * 1. `PARE_TOOLS` — universal filter across all servers.
7
+ * Format: comma-separated `server:tool` pairs.
8
+ * Example: `PARE_TOOLS=git:status,git:log,npm:install`
9
+ *
10
+ * 2. `PARE_{SERVER}_TOOLS` — per-server filter.
11
+ * Format: comma-separated tool names.
12
+ * Example: `PARE_GIT_TOOLS=status,log`
13
+ *
14
+ * 3. No env vars → all tools enabled (default).
15
+ *
16
+ * Universal (`PARE_TOOLS`) takes precedence over per-server env vars.
17
+ */
18
+ /**
19
+ * Determines whether a tool should be registered based on environment variables.
20
+ *
21
+ * @param serverName - The server identifier (e.g., "git", "npm", "docker").
22
+ * @param toolName - The tool name (e.g., "status", "log", "install").
23
+ * @returns `true` if the tool should be registered, `false` if filtered out.
24
+ */
25
+ export declare function shouldRegisterTool(serverName: string, toolName: string): boolean;
26
+ //# sourceMappingURL=tool-filter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-filter.d.ts","sourceRoot":"","sources":["../src/tool-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;GAMG;AACH,wBAAgB,kBAAkB,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAiBhF"}
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Granular tool selection — allows users to enable/disable individual tools
3
+ * via environment variables.
4
+ *
5
+ * Environment variable precedence:
6
+ * 1. `PARE_TOOLS` — universal filter across all servers.
7
+ * Format: comma-separated `server:tool` pairs.
8
+ * Example: `PARE_TOOLS=git:status,git:log,npm:install`
9
+ *
10
+ * 2. `PARE_{SERVER}_TOOLS` — per-server filter.
11
+ * Format: comma-separated tool names.
12
+ * Example: `PARE_GIT_TOOLS=status,log`
13
+ *
14
+ * 3. No env vars → all tools enabled (default).
15
+ *
16
+ * Universal (`PARE_TOOLS`) takes precedence over per-server env vars.
17
+ */
18
+ /**
19
+ * Determines whether a tool should be registered based on environment variables.
20
+ *
21
+ * @param serverName - The server identifier (e.g., "git", "npm", "docker").
22
+ * @param toolName - The tool name (e.g., "status", "log", "install").
23
+ * @returns `true` if the tool should be registered, `false` if filtered out.
24
+ */
25
+ export function shouldRegisterTool(serverName, toolName) {
26
+ const universal = process.env.PARE_TOOLS;
27
+ if (universal !== undefined) {
28
+ if (universal.trim() === "")
29
+ return false;
30
+ const allowed = universal.split(",").map((s) => s.trim());
31
+ return allowed.includes(`${serverName}:${toolName}`);
32
+ }
33
+ const envKey = `PARE_${serverName.toUpperCase().replace(/-/g, "_")}_TOOLS`;
34
+ const perServer = process.env[envKey];
35
+ if (perServer !== undefined) {
36
+ if (perServer.trim() === "")
37
+ return false;
38
+ const allowed = perServer.split(",").map((s) => s.trim());
39
+ return allowed.includes(toolName);
40
+ }
41
+ return true;
42
+ }
43
+ //# sourceMappingURL=tool-filter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tool-filter.js","sourceRoot":"","sources":["../src/tool-filter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH;;;;;;GAMG;AACH,MAAM,UAAU,kBAAkB,CAAC,UAAkB,EAAE,QAAgB;IACrE,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC;IACzC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1D,OAAO,OAAO,CAAC,QAAQ,CAAC,GAAG,UAAU,IAAI,QAAQ,EAAE,CAAC,CAAC;IACvD,CAAC;IAED,MAAM,MAAM,GAAG,QAAQ,UAAU,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,GAAG,CAAC,QAAQ,CAAC;IAC3E,MAAM,SAAS,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;IACtC,IAAI,SAAS,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,SAAS,CAAC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,KAAK,CAAC;QAC1C,MAAM,OAAO,GAAG,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC;QAC1D,OAAO,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IACpC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -4,6 +4,18 @@
4
4
  * See: CVE-2025-68144, CVE-2025-68145
5
5
  */
6
6
  export declare function assertNoFlagInjection(value: string, paramName: string): void;
7
- /** Validates that a command is in the allowlist of safe build tools to prevent arbitrary command execution. */
7
+ /**
8
+ * Validates that a command is in the allowlist of safe build tools to prevent arbitrary command execution.
9
+ *
10
+ * Known limitation: only the basename is checked, so a path like `/tmp/evil/npm` would pass.
11
+ * Rejecting paths entirely would break legitimate use cases (NixOS store paths, `C:\Program Files\...`,
12
+ * non-PATH installs). Exploiting this requires placing a malicious binary on disk, which already
13
+ * implies the system is compromised.
14
+ *
15
+ * Security note: When a full path is provided (containing `/` or `\`), a warning is logged because
16
+ * basename-only validation cannot guarantee the binary at that path is the genuine tool. An attacker
17
+ * with write access to the filesystem could place a malicious binary at a path like `/tmp/evil/npm`.
18
+ * The warning serves as an audit trail for security-conscious deployments.
19
+ */
8
20
  export declare function assertAllowedCommand(command: string): void;
9
21
  //# sourceMappingURL=validation.d.ts.map
@@ -1 +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,+GAA+G;AAC/G,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAe1D"}
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;;;;;;;;;;;;GAYG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,CAwB1D"}
@@ -4,7 +4,7 @@
4
4
  * See: CVE-2025-68144, CVE-2025-68145
5
5
  */
6
6
  export function assertNoFlagInjection(value, paramName) {
7
- if (value.startsWith("-")) {
7
+ if (value.trimStart().startsWith("-")) {
8
8
  throw new Error(`Invalid ${paramName}: "${value}". Values must not start with "-" to prevent argument injection.`);
9
9
  }
10
10
  }
@@ -38,7 +38,19 @@ const ALLOWED_BUILD_COMMANDS = new Set([
38
38
  "nx",
39
39
  "bazel",
40
40
  ]);
41
- /** Validates that a command is in the allowlist of safe build tools to prevent arbitrary command execution. */
41
+ /**
42
+ * Validates that a command is in the allowlist of safe build tools to prevent arbitrary command execution.
43
+ *
44
+ * Known limitation: only the basename is checked, so a path like `/tmp/evil/npm` would pass.
45
+ * Rejecting paths entirely would break legitimate use cases (NixOS store paths, `C:\Program Files\...`,
46
+ * non-PATH installs). Exploiting this requires placing a malicious binary on disk, which already
47
+ * implies the system is compromised.
48
+ *
49
+ * Security note: When a full path is provided (containing `/` or `\`), a warning is logged because
50
+ * basename-only validation cannot guarantee the binary at that path is the genuine tool. An attacker
51
+ * with write access to the filesystem could place a malicious binary at a path like `/tmp/evil/npm`.
52
+ * The warning serves as an audit trail for security-conscious deployments.
53
+ */
42
54
  export function assertAllowedCommand(command) {
43
55
  // Extract the base command name (handle paths like /usr/bin/npm or C:\npm.cmd)
44
56
  const base = command
@@ -50,5 +62,11 @@ export function assertAllowedCommand(command) {
50
62
  throw new Error(`Command "${command}" is not in the allowed build commands list. ` +
51
63
  `Allowed: ${[...ALLOWED_BUILD_COMMANDS].sort().join(", ")}`);
52
64
  }
65
+ // Warn when a full path is used — basename-only validation cannot verify the actual binary
66
+ if (command.includes("/") || command.includes("\\")) {
67
+ console.warn(`[pare:security] Command uses a full path: "${command}". ` +
68
+ `Only the basename "${base}" was validated against the allowlist. ` +
69
+ `Ensure this path points to a trusted binary.`);
70
+ }
53
71
  }
54
72
  //# sourceMappingURL=validation.js.map
@@ -1 +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,+GAA+G;AAC/G,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"}
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,SAAS,EAAE,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QACtC,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;;;;;;;;;;;;GAYG;AACH,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;IAED,2FAA2F;IAC3F,IAAI,OAAO,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC;QACpD,OAAO,CAAC,IAAI,CACV,8CAA8C,OAAO,KAAK;YACxD,sBAAsB,IAAI,yCAAyC;YACnE,8CAA8C,CACjD,CAAC;IACJ,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@paretools/shared",
3
- "version": "0.5.0",
3
+ "version": "0.7.0",
4
4
  "description": "Shared utilities for Pare MCP servers",
5
5
  "license": "MIT",
6
6
  "keywords": [
@@ -14,7 +14,7 @@
14
14
  "devtools",
15
15
  "cli"
16
16
  ],
17
- "homepage": "https://github.com/Dave-London/pare/tree/main/packages/shared",
17
+ "homepage": "https://github.com/Dave-London/Pare/tree/main/packages/shared",
18
18
  "engines": {
19
19
  "node": ">=20"
20
20
  },
@@ -30,7 +30,7 @@
30
30
  },
31
31
  "repository": {
32
32
  "type": "git",
33
- "url": "https://github.com/Dave-London/pare.git",
33
+ "url": "https://github.com/Dave-London/Pare.git",
34
34
  "directory": "packages/shared"
35
35
  },
36
36
  "files": [