procsi 0.4.1 → 0.6.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.
Files changed (125) hide show
  1. package/dist/cli/commands/on.d.ts.map +1 -1
  2. package/dist/cli/commands/on.js +1 -0
  3. package/dist/cli/commands/on.js.map +1 -1
  4. package/dist/cli/commands/request.d.ts.map +1 -1
  5. package/dist/cli/commands/request.js +97 -2
  6. package/dist/cli/commands/request.js.map +1 -1
  7. package/dist/cli/commands/requests.d.ts +25 -0
  8. package/dist/cli/commands/requests.d.ts.map +1 -1
  9. package/dist/cli/commands/requests.js +35 -4
  10. package/dist/cli/commands/requests.js.map +1 -1
  11. package/dist/cli/formatters/detail.d.ts.map +1 -1
  12. package/dist/cli/formatters/detail.js +6 -0
  13. package/dist/cli/formatters/detail.js.map +1 -1
  14. package/dist/cli/formatters/table.d.ts.map +1 -1
  15. package/dist/cli/formatters/table.js +11 -1
  16. package/dist/cli/formatters/table.js.map +1 -1
  17. package/dist/cli/tui/App.d.ts.map +1 -1
  18. package/dist/cli/tui/App.js +81 -28
  19. package/dist/cli/tui/App.js.map +1 -1
  20. package/dist/cli/tui/components/AccordionPanel.d.ts.map +1 -1
  21. package/dist/cli/tui/components/AccordionPanel.js +2 -1
  22. package/dist/cli/tui/components/AccordionPanel.js.map +1 -1
  23. package/dist/cli/tui/components/FilterBar.d.ts +10 -2
  24. package/dist/cli/tui/components/FilterBar.d.ts.map +1 -1
  25. package/dist/cli/tui/components/FilterBar.js +100 -12
  26. package/dist/cli/tui/components/FilterBar.js.map +1 -1
  27. package/dist/cli/tui/components/HelpModal.d.ts +6 -2
  28. package/dist/cli/tui/components/HelpModal.d.ts.map +1 -1
  29. package/dist/cli/tui/components/HelpModal.js +19 -46
  30. package/dist/cli/tui/components/HelpModal.js.map +1 -1
  31. package/dist/cli/tui/components/RequestListItem.d.ts +8 -0
  32. package/dist/cli/tui/components/RequestListItem.d.ts.map +1 -1
  33. package/dist/cli/tui/components/RequestListItem.js +14 -2
  34. package/dist/cli/tui/components/RequestListItem.js.map +1 -1
  35. package/dist/cli/tui/components/StatusBar.d.ts +2 -9
  36. package/dist/cli/tui/components/StatusBar.d.ts.map +1 -1
  37. package/dist/cli/tui/components/StatusBar.js +7 -13
  38. package/dist/cli/tui/components/StatusBar.js.map +1 -1
  39. package/dist/cli/tui/hooks/useExport.d.ts +10 -2
  40. package/dist/cli/tui/hooks/useExport.d.ts.map +1 -1
  41. package/dist/cli/tui/hooks/useExport.js +33 -8
  42. package/dist/cli/tui/hooks/useExport.js.map +1 -1
  43. package/dist/cli/tui/hooks/useRequests.d.ts +4 -1
  44. package/dist/cli/tui/hooks/useRequests.d.ts.map +1 -1
  45. package/dist/cli/tui/hooks/useRequests.js +36 -9
  46. package/dist/cli/tui/hooks/useRequests.js.map +1 -1
  47. package/dist/cli/tui/hooks/useStdoutDimensions.d.ts +4 -0
  48. package/dist/cli/tui/hooks/useStdoutDimensions.d.ts.map +1 -1
  49. package/dist/cli/tui/hooks/useStdoutDimensions.js +20 -10
  50. package/dist/cli/tui/hooks/useStdoutDimensions.js.map +1 -1
  51. package/dist/cli/tui/utils/curl.d.ts.map +1 -1
  52. package/dist/cli/tui/utils/curl.js +6 -13
  53. package/dist/cli/tui/utils/curl.js.map +1 -1
  54. package/dist/cli/tui/utils/export-shared.d.ts +9 -0
  55. package/dist/cli/tui/utils/export-shared.d.ts.map +1 -0
  56. package/dist/cli/tui/utils/export-shared.js +15 -0
  57. package/dist/cli/tui/utils/export-shared.js.map +1 -0
  58. package/dist/cli/tui/utils/fetch.d.ts +9 -0
  59. package/dist/cli/tui/utils/fetch.d.ts.map +1 -0
  60. package/dist/cli/tui/utils/fetch.js +77 -0
  61. package/dist/cli/tui/utils/fetch.js.map +1 -0
  62. package/dist/cli/tui/utils/filters.d.ts.map +1 -1
  63. package/dist/cli/tui/utils/filters.js +1 -0
  64. package/dist/cli/tui/utils/filters.js.map +1 -1
  65. package/dist/cli/tui/utils/httpie.d.ts +9 -0
  66. package/dist/cli/tui/utils/httpie.d.ts.map +1 -0
  67. package/dist/cli/tui/utils/httpie.js +78 -0
  68. package/dist/cli/tui/utils/httpie.js.map +1 -0
  69. package/dist/cli/tui/utils/python-requests.d.ts +19 -0
  70. package/dist/cli/tui/utils/python-requests.d.ts.map +1 -0
  71. package/dist/cli/tui/utils/python-requests.js +101 -0
  72. package/dist/cli/tui/utils/python-requests.js.map +1 -0
  73. package/dist/cli/tui/utils/terminal-size.d.ts +20 -0
  74. package/dist/cli/tui/utils/terminal-size.d.ts.map +1 -0
  75. package/dist/cli/tui/utils/terminal-size.js +100 -0
  76. package/dist/cli/tui/utils/terminal-size.js.map +1 -0
  77. package/dist/daemon/control.d.ts +3 -0
  78. package/dist/daemon/control.d.ts.map +1 -1
  79. package/dist/daemon/control.js +199 -6
  80. package/dist/daemon/control.js.map +1 -1
  81. package/dist/daemon/index.js +7 -0
  82. package/dist/daemon/index.js.map +1 -1
  83. package/dist/daemon/procsi-client.d.ts.map +1 -1
  84. package/dist/daemon/procsi-client.js +1 -0
  85. package/dist/daemon/procsi-client.js.map +1 -1
  86. package/dist/daemon/proxy.d.ts +3 -0
  87. package/dist/daemon/proxy.d.ts.map +1 -1
  88. package/dist/daemon/proxy.js +19 -6
  89. package/dist/daemon/proxy.js.map +1 -1
  90. package/dist/daemon/replay-tracker.d.ts +11 -0
  91. package/dist/daemon/replay-tracker.d.ts.map +1 -0
  92. package/dist/daemon/replay-tracker.js +47 -0
  93. package/dist/daemon/replay-tracker.js.map +1 -0
  94. package/dist/daemon/replay.d.ts +20 -0
  95. package/dist/daemon/replay.d.ts.map +1 -0
  96. package/dist/daemon/replay.js +113 -0
  97. package/dist/daemon/replay.js.map +1 -0
  98. package/dist/daemon/storage.d.ts +16 -2
  99. package/dist/daemon/storage.d.ts.map +1 -1
  100. package/dist/daemon/storage.js +104 -7
  101. package/dist/daemon/storage.js.map +1 -1
  102. package/dist/mcp/server.d.ts +3 -0
  103. package/dist/mcp/server.d.ts.map +1 -1
  104. package/dist/mcp/server.js +183 -4
  105. package/dist/mcp/server.js.map +1 -1
  106. package/dist/shared/body-search.d.ts +28 -0
  107. package/dist/shared/body-search.d.ts.map +1 -0
  108. package/dist/shared/body-search.js +64 -0
  109. package/dist/shared/body-search.js.map +1 -0
  110. package/dist/shared/constants.d.ts +2 -0
  111. package/dist/shared/constants.d.ts.map +1 -1
  112. package/dist/shared/constants.js +2 -0
  113. package/dist/shared/constants.js.map +1 -1
  114. package/dist/shared/control-client.d.ts +23 -2
  115. package/dist/shared/control-client.d.ts.map +1 -1
  116. package/dist/shared/control-client.js +15 -2
  117. package/dist/shared/control-client.js.map +1 -1
  118. package/dist/shared/regex-filter.d.ts +35 -0
  119. package/dist/shared/regex-filter.d.ts.map +1 -0
  120. package/dist/shared/regex-filter.js +86 -0
  121. package/dist/shared/regex-filter.js.map +1 -0
  122. package/dist/shared/types.d.ts +13 -0
  123. package/dist/shared/types.d.ts.map +1 -1
  124. package/package.json +2 -1
  125. package/skills/procsi/SKILL.md +9 -4
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Generate HTTPie commands from captured requests.
3
+ */
4
+ import { EXCLUDED_HEADERS } from "./export-shared.js";
5
+ import { isJsonContent } from "./content-type.js";
6
+ /**
7
+ * Escape a string for use in a shell single-quoted context.
8
+ * Same approach as curl: end the quote, insert an escaped single quote, reopen.
9
+ * Null bytes are stripped to prevent shell truncation.
10
+ */
11
+ function shellEscape(str) {
12
+ return str.replace(/\0/g, "").replace(/'/g, "'\"'\"'");
13
+ }
14
+ /**
15
+ * Generate an HTTPie command from a captured request.
16
+ */
17
+ export function generateHttpie(request) {
18
+ const parts = ["http"];
19
+ // HTTPie uses the method as the first argument (defaults to GET without body, POST with body)
20
+ // Always include the method for clarity
21
+ parts.push(request.method);
22
+ // Add URL
23
+ parts.push(`'${shellEscape(request.url)}'`);
24
+ // Add headers using Name:Value syntax
25
+ for (const [name, value] of Object.entries(request.requestHeaders)) {
26
+ if (EXCLUDED_HEADERS.has(name.toLowerCase())) {
27
+ continue;
28
+ }
29
+ parts.push(`'${shellEscape(name)}:${shellEscape(value)}'`);
30
+ }
31
+ // Add body if present and method is not GET/HEAD
32
+ if (request.requestBody &&
33
+ request.requestBody.length > 0 &&
34
+ request.method !== "GET" &&
35
+ request.method !== "HEAD") {
36
+ const bodyStr = request.requestBody.toString("utf-8");
37
+ const contentType = request.requestHeaders["content-type"];
38
+ if (isJsonContent(contentType)) {
39
+ try {
40
+ const parsed = JSON.parse(bodyStr);
41
+ // Only use key=value / key:=value syntax for flat objects
42
+ if (parsed !== null && typeof parsed === "object" && !Array.isArray(parsed)) {
43
+ const entries = Object.entries(parsed);
44
+ const allFlat = entries.every(([, v]) => typeof v === "string" || typeof v === "number" || typeof v === "boolean" || v === null);
45
+ if (allFlat) {
46
+ for (const [key, val] of entries) {
47
+ if (typeof val === "string") {
48
+ // key=value for strings
49
+ parts.push(`'${shellEscape(key)}=${shellEscape(val)}'`);
50
+ }
51
+ else {
52
+ // key:=value for non-strings (numbers, booleans, null)
53
+ parts.push(`'${shellEscape(key)}:=${JSON.stringify(val)}'`);
54
+ }
55
+ }
56
+ }
57
+ else {
58
+ // Non-flat object — use --raw
59
+ parts.push(`--raw='${shellEscape(bodyStr)}'`);
60
+ }
61
+ }
62
+ else {
63
+ // Non-object JSON (array, primitive) — use --raw
64
+ parts.push(`--raw='${shellEscape(bodyStr)}'`);
65
+ }
66
+ }
67
+ catch {
68
+ // Invalid JSON — use --raw
69
+ parts.push(`--raw='${shellEscape(bodyStr)}'`);
70
+ }
71
+ }
72
+ else {
73
+ parts.push(`--raw='${shellEscape(bodyStr)}'`);
74
+ }
75
+ }
76
+ return parts.join(" \\\n ");
77
+ }
78
+ //# sourceMappingURL=httpie.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"httpie.js","sourceRoot":"","sources":["../../../../src/cli/tui/utils/httpie.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Generate Python requests library calls from captured requests.
3
+ */
4
+ import type { CapturedRequest } from "../../../shared/types.js";
5
+ /**
6
+ * Escape a string for use inside a Python single-quoted string literal.
7
+ * Null bytes are stripped to prevent interpreter issues.
8
+ */
9
+ export declare function escapePythonString(str: string): string;
10
+ /**
11
+ * Convert a JavaScript value to its Python repr equivalent.
12
+ * Handles strings, numbers, booleans, null, arrays, and objects.
13
+ */
14
+ export declare function pythonRepr(value: unknown): string;
15
+ /**
16
+ * Generate a Python requests call from a captured request.
17
+ */
18
+ export declare function generatePythonRequests(request: CapturedRequest): string;
19
+ //# sourceMappingURL=python-requests.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-requests.d.ts","sourceRoot":"","sources":["../../../../src/cli/tui/utils/python-requests.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAIhE;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CAQtD;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CA0BjD;AAED;;GAEG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,eAAe,GAAG,MAAM,CAwDvE"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Generate Python requests library calls from captured requests.
3
+ */
4
+ import { EXCLUDED_HEADERS } from "./export-shared.js";
5
+ import { isJsonContent } from "./content-type.js";
6
+ /**
7
+ * Escape a string for use inside a Python single-quoted string literal.
8
+ * Null bytes are stripped to prevent interpreter issues.
9
+ */
10
+ export function escapePythonString(str) {
11
+ return str
12
+ .replace(/\0/g, "")
13
+ .replace(/\\/g, "\\\\")
14
+ .replace(/'/g, "\\'")
15
+ .replace(/\n/g, "\\n")
16
+ .replace(/\r/g, "\\r")
17
+ .replace(/\t/g, "\\t");
18
+ }
19
+ /**
20
+ * Convert a JavaScript value to its Python repr equivalent.
21
+ * Handles strings, numbers, booleans, null, arrays, and objects.
22
+ */
23
+ export function pythonRepr(value) {
24
+ if (value === null || value === undefined) {
25
+ return "None";
26
+ }
27
+ if (value === true) {
28
+ return "True";
29
+ }
30
+ if (value === false) {
31
+ return "False";
32
+ }
33
+ if (typeof value === "number") {
34
+ return String(value);
35
+ }
36
+ if (typeof value === "string") {
37
+ return `'${escapePythonString(value)}'`;
38
+ }
39
+ if (Array.isArray(value)) {
40
+ const items = value.map((item) => pythonRepr(item));
41
+ return `[${items.join(", ")}]`;
42
+ }
43
+ if (typeof value === "object") {
44
+ const entries = Object.entries(value);
45
+ const pairs = entries.map(([k, v]) => `${pythonRepr(k)}: ${pythonRepr(v)}`);
46
+ return `{${pairs.join(", ")}}`;
47
+ }
48
+ return String(value);
49
+ }
50
+ /**
51
+ * Generate a Python requests call from a captured request.
52
+ */
53
+ export function generatePythonRequests(request) {
54
+ const lines = ["import requests", ""];
55
+ const method = request.method.toLowerCase();
56
+ const args = [`'${escapePythonString(request.url)}'`];
57
+ // Collect headers (excluding automatic ones)
58
+ const headers = [];
59
+ const contentType = request.requestHeaders["content-type"];
60
+ const usingJsonKwarg = isJsonContent(contentType);
61
+ for (const [name, value] of Object.entries(request.requestHeaders)) {
62
+ if (EXCLUDED_HEADERS.has(name.toLowerCase())) {
63
+ continue;
64
+ }
65
+ // When using json= kwarg, requests sets content-type automatically
66
+ if (usingJsonKwarg && name.toLowerCase() === "content-type") {
67
+ continue;
68
+ }
69
+ headers.push([name, value]);
70
+ }
71
+ if (headers.length > 0) {
72
+ const headerPairs = headers
73
+ .map(([k, v]) => ` '${escapePythonString(k)}': '${escapePythonString(v)}'`)
74
+ .join(",\n");
75
+ args.push(`headers={\n${headerPairs}\n}`);
76
+ }
77
+ // Add body if present and method is not GET/HEAD
78
+ if (request.requestBody &&
79
+ request.requestBody.length > 0 &&
80
+ request.method !== "GET" &&
81
+ request.method !== "HEAD") {
82
+ const bodyStr = request.requestBody.toString("utf-8");
83
+ if (usingJsonKwarg) {
84
+ try {
85
+ const parsed = JSON.parse(bodyStr);
86
+ args.push(`json=${pythonRepr(parsed)}`);
87
+ }
88
+ catch {
89
+ // Invalid JSON — fall back to data kwarg
90
+ args.push(`data='${escapePythonString(bodyStr)}'`);
91
+ }
92
+ }
93
+ else {
94
+ args.push(`data='${escapePythonString(bodyStr)}'`);
95
+ }
96
+ }
97
+ const argsStr = args.length === 1 ? args[0] : `\n ${args.join(",\n ")}\n`;
98
+ lines.push(`response = requests.${method}(${argsStr})`);
99
+ return lines.join("\n");
100
+ }
101
+ //# sourceMappingURL=python-requests.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"python-requests.js","sourceRoot":"","sources":["../../../../src/cli/tui/utils/python-requests.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ interface StdoutLike {
2
+ columns?: number;
3
+ rows?: number;
4
+ getWindowSize?: () => [number, number];
5
+ emit?: (event: string) => boolean;
6
+ }
7
+ type ReadFallbackDimensions = () => [number, number] | undefined;
8
+ /**
9
+ * Synchronise stdout.columns/rows and emit a resize event so Ink recalculates
10
+ * layout using current terminal dimensions.
11
+ *
12
+ * Primary source: stdout.getWindowSize().
13
+ * Fallback sources: `stty size` against /dev/tty, then COLUMNS/LINES env vars
14
+ * (for terminals where Node's width/height sources lag until later interaction).
15
+ *
16
+ * Returns true when valid dimensions were applied.
17
+ */
18
+ export declare function syncStdoutDimensions(stdout: StdoutLike, readFallbackDimensions?: ReadFallbackDimensions): boolean;
19
+ export {};
20
+ //# sourceMappingURL=terminal-size.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-size.d.ts","sourceRoot":"","sources":["../../../../src/cli/tui/utils/terminal-size.ts"],"names":[],"mappings":"AAGA,UAAU,UAAU;IAClB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACvC,IAAI,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC;CACnC;AAED,KAAK,sBAAsB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,SAAS,CAAC;AA2EjE;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,UAAU,EAClB,sBAAsB,GAAE,sBAA+C,GACtE,OAAO,CA4BT"}
@@ -0,0 +1,100 @@
1
+ import { closeSync, openSync } from "node:fs";
2
+ import { spawnSync } from "node:child_process";
3
+ function isPositiveInteger(value) {
4
+ return typeof value === "number" && Number.isInteger(value) && value > 0;
5
+ }
6
+ function hasValidDimensions(dimensions) {
7
+ if (!dimensions)
8
+ return false;
9
+ const [columns, rows] = dimensions;
10
+ return isPositiveInteger(columns) && isPositiveInteger(rows);
11
+ }
12
+ function readDimensionsFromWindowSize(stdout) {
13
+ if (typeof stdout.getWindowSize !== "function") {
14
+ return undefined;
15
+ }
16
+ try {
17
+ return stdout.getWindowSize();
18
+ }
19
+ catch {
20
+ return undefined;
21
+ }
22
+ }
23
+ function readDimensionsFromEnv() {
24
+ const columns = Number(process.env["COLUMNS"]);
25
+ const rows = Number(process.env["LINES"]);
26
+ if (!isPositiveInteger(columns) || !isPositiveInteger(rows)) {
27
+ return undefined;
28
+ }
29
+ return [columns, rows];
30
+ }
31
+ function readDimensionsFromStty() {
32
+ if (process.platform === "win32") {
33
+ return undefined;
34
+ }
35
+ let ttyFd;
36
+ try {
37
+ ttyFd = openSync("/dev/tty", "r");
38
+ const result = spawnSync("stty", ["size"], {
39
+ stdio: [ttyFd, "pipe", "ignore"],
40
+ encoding: "utf-8",
41
+ });
42
+ if (result.status !== 0 || !result.stdout) {
43
+ return undefined;
44
+ }
45
+ const output = result.stdout.trim();
46
+ const parts = output.split(/\s+/);
47
+ if (parts.length !== 2) {
48
+ return undefined;
49
+ }
50
+ const rows = Number(parts[0]);
51
+ const columns = Number(parts[1]);
52
+ if (!isPositiveInteger(columns) || !isPositiveInteger(rows)) {
53
+ return undefined;
54
+ }
55
+ return [columns, rows];
56
+ }
57
+ catch {
58
+ return undefined;
59
+ }
60
+ finally {
61
+ if (ttyFd !== undefined) {
62
+ closeSync(ttyFd);
63
+ }
64
+ }
65
+ }
66
+ /**
67
+ * Synchronise stdout.columns/rows and emit a resize event so Ink recalculates
68
+ * layout using current terminal dimensions.
69
+ *
70
+ * Primary source: stdout.getWindowSize().
71
+ * Fallback sources: `stty size` against /dev/tty, then COLUMNS/LINES env vars
72
+ * (for terminals where Node's width/height sources lag until later interaction).
73
+ *
74
+ * Returns true when valid dimensions were applied.
75
+ */
76
+ export function syncStdoutDimensions(stdout, readFallbackDimensions = readDimensionsFromStty) {
77
+ const fromFallback = readFallbackDimensions();
78
+ const fromEnv = readDimensionsFromEnv();
79
+ const fromWindowSize = readDimensionsFromWindowSize(stdout);
80
+ // Prefer /dev/tty + stty when available: in some terminals this reports
81
+ // the correct startup size earlier than Node's tty stream values.
82
+ const dimensions = hasValidDimensions(fromFallback)
83
+ ? fromFallback
84
+ : hasValidDimensions(fromEnv)
85
+ ? fromEnv
86
+ : fromWindowSize;
87
+ if (!hasValidDimensions(dimensions)) {
88
+ return false;
89
+ }
90
+ const [columns, rows] = dimensions;
91
+ stdout.columns = columns;
92
+ stdout.rows = rows;
93
+ process.env["COLUMNS"] = String(columns);
94
+ process.env["LINES"] = String(rows);
95
+ if (typeof stdout.emit === "function") {
96
+ stdout.emit("resize");
97
+ }
98
+ return true;
99
+ }
100
+ //# sourceMappingURL=terminal-size.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"terminal-size.js","sourceRoot":"","sources":["../../../../src/cli/tui/utils/terminal-size.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,6 @@
1
1
  import type { RequestRepository } from "./storage.js";
2
2
  import type { InterceptorLoader } from "./interceptor-loader.js";
3
+ import type { ReplayTracker } from "./replay-tracker.js";
3
4
  import type { InterceptorEventLog } from "./interceptor-event-log.js";
4
5
  import { type LogLevel } from "../shared/logger.js";
5
6
  export { ControlClient } from "../shared/control-client.js";
@@ -12,6 +13,8 @@ export interface ControlServerOptions {
12
13
  logLevel?: LogLevel;
13
14
  interceptorLoader?: InterceptorLoader;
14
15
  interceptorEventLog?: InterceptorEventLog;
16
+ replayTracker?: ReplayTracker;
17
+ caCertPem?: string;
15
18
  }
16
19
  export interface ControlServer {
17
20
  close: () => Promise<void>;
@@ -1 +1 @@
1
- {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../src/daemon/control.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AAcjE,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,4BAA4B,CAAC;AAMnF,OAAO,EAAgB,KAAK,QAAQ,EAAe,MAAM,qBAAqB,CAAC;AAG/E,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAE5D,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAoID;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CA0ShF"}
1
+ {"version":3,"file":"control.d.ts","sourceRoot":"","sources":["../../src/daemon/control.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,cAAc,CAAC;AACtD,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,yBAAyB,CAAC;AACjE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,qBAAqB,CAAC;AAiBzD,OAAO,KAAK,EAAE,mBAAmB,EAAe,MAAM,4BAA4B,CAAC;AAMnF,OAAO,EAAgB,KAAK,QAAQ,EAAe,MAAM,qBAAqB,CAAC;AAI/E,OAAO,EAAE,aAAa,EAAE,MAAM,6BAA6B,CAAC;AAI5D,MAAM,WAAW,oBAAoB;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,iBAAiB,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,EAAE,QAAQ,CAAC;IACpB,iBAAiB,CAAC,EAAE,iBAAiB,CAAC;IACtC,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;IAC1C,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAC;CAC5B;AAySD;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,oBAAoB,GAAG,aAAa,CAoXhF"}
@@ -1,9 +1,13 @@
1
1
  import * as net from "node:net";
2
2
  import * as fs from "node:fs";
3
+ import { randomBytes } from "node:crypto";
4
+ import { buildReplayHeaders, replayViaProxy } from "./replay.js";
3
5
  import { MAX_BUFFER_SIZE, } from "../shared/control-client.js";
4
6
  import { createLogger } from "../shared/logger.js";
5
7
  import { resolveProcessName } from "../shared/process-name.js";
8
+ import { parseBodySearchTarget } from "../shared/body-search.js";
6
9
  export { ControlClient } from "../shared/control-client.js";
10
+ const REPLAY_TOKEN_BYTES = 16;
7
11
  /**
8
12
  * Runtime type guard for incoming control messages.
9
13
  */
@@ -31,6 +35,97 @@ function requireString(params, key) {
31
35
  }
32
36
  return value;
33
37
  }
38
+ function optionalStringRecord(params, key) {
39
+ const value = params[key];
40
+ if (value === undefined)
41
+ return undefined;
42
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
43
+ throw new Error(`Invalid ${key} parameter: expected object of string values`);
44
+ }
45
+ const result = {};
46
+ for (const [entryKey, entryValue] of Object.entries(value)) {
47
+ if (typeof entryValue !== "string") {
48
+ throw new Error(`Invalid ${key}.${entryKey}: expected string value`);
49
+ }
50
+ result[entryKey] = entryValue;
51
+ }
52
+ return result;
53
+ }
54
+ function optionalStringArray(params, key) {
55
+ const value = params[key];
56
+ if (value === undefined)
57
+ return undefined;
58
+ if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string")) {
59
+ throw new Error(`Invalid ${key} parameter: expected array of strings`);
60
+ }
61
+ return value;
62
+ }
63
+ function optionalReplayInitiator(params, key) {
64
+ const value = params[key];
65
+ if (value === undefined)
66
+ return undefined;
67
+ if (value === "mcp" || value === "tui" || value === "cli") {
68
+ return value;
69
+ }
70
+ throw new Error(`Invalid ${key} parameter: expected "mcp", "tui", or "cli"`);
71
+ }
72
+ function optionalBodySearchTarget(params, key) {
73
+ const value = params[key];
74
+ if (value === undefined)
75
+ return undefined;
76
+ if (typeof value !== "string") {
77
+ throw new Error(`Invalid ${key} parameter: expected string`);
78
+ }
79
+ const target = parseBodySearchTarget(value);
80
+ if (!target) {
81
+ throw new Error(`Invalid ${key} parameter: "${value}". Use request, response, or both.`);
82
+ }
83
+ return target;
84
+ }
85
+ function optionalQueryTarget(params, key) {
86
+ const value = params[key];
87
+ if (value === undefined)
88
+ return undefined;
89
+ if (value === "request" || value === "response" || value === "both") {
90
+ return value;
91
+ }
92
+ throw new Error(`Invalid ${key} parameter: "${String(value)}". Use "request", "response", or "both".`);
93
+ }
94
+ const VALID_INTERCEPTOR_EVENT_LEVELS = new Set(["info", "warn", "error"]);
95
+ function optionalInterceptorEventLevel(params, key) {
96
+ const value = params[key];
97
+ if (value === undefined)
98
+ return undefined;
99
+ if (typeof value === "string" && VALID_INTERCEPTOR_EVENT_LEVELS.has(value)) {
100
+ return value;
101
+ }
102
+ throw new Error(`Invalid ${key} parameter: "${String(value)}". Use "info", "warn", or "error".`);
103
+ }
104
+ const VALID_INTERCEPTOR_EVENT_TYPES = new Set([
105
+ "matched",
106
+ "mocked",
107
+ "modified",
108
+ "observed",
109
+ "match_error",
110
+ "match_timeout",
111
+ "handler_error",
112
+ "handler_timeout",
113
+ "invalid_response",
114
+ "forward_after_complete",
115
+ "load_error",
116
+ "loaded",
117
+ "reload",
118
+ "user_log",
119
+ ]);
120
+ function optionalInterceptorEventType(params, key) {
121
+ const value = params[key];
122
+ if (value === undefined)
123
+ return undefined;
124
+ if (typeof value === "string" && VALID_INTERCEPTOR_EVENT_TYPES.has(value)) {
125
+ return value;
126
+ }
127
+ throw new Error(`Invalid ${key} parameter: "${String(value)}". Expected a valid interceptor event type.`);
128
+ }
34
129
  /**
35
130
  * Validate an optional RequestFilter from untyped control message params.
36
131
  */
@@ -52,6 +147,12 @@ function optionalFilter(params) {
52
147
  if (typeof f["search"] === "string") {
53
148
  result.search = f["search"];
54
149
  }
150
+ if (typeof f["regex"] === "string") {
151
+ result.regex = f["regex"];
152
+ }
153
+ if (typeof f["regexFlags"] === "string") {
154
+ result.regexFlags = f["regexFlags"];
155
+ }
55
156
  if (typeof f["host"] === "string") {
56
157
  result.host = f["host"];
57
158
  }
@@ -86,11 +187,38 @@ function optionalFilter(params) {
86
187
  }
87
188
  return Object.keys(result).length > 0 ? result : undefined;
88
189
  }
190
+ function parseOptionalReplayBody(params) {
191
+ const bodyText = optionalString(params, "body");
192
+ const bodyBase64 = optionalString(params, "bodyBase64");
193
+ if (bodyText !== undefined && bodyBase64 !== undefined) {
194
+ throw new Error('Provide either "body" or "bodyBase64", not both.');
195
+ }
196
+ if (bodyBase64 !== undefined) {
197
+ const BASE64_PATTERN = /^(?:[A-Za-z0-9+/]{4})*(?:[A-Za-z0-9+/]{2}==|[A-Za-z0-9+/]{3}=)?$/;
198
+ if (!BASE64_PATTERN.test(bodyBase64)) {
199
+ throw new Error("Invalid bodyBase64 parameter: expected valid base64 content");
200
+ }
201
+ return {
202
+ body: Buffer.from(bodyBase64, "base64"),
203
+ hasExplicitBody: true,
204
+ };
205
+ }
206
+ if (bodyText !== undefined) {
207
+ return {
208
+ body: Buffer.from(bodyText, "utf-8"),
209
+ hasExplicitBody: true,
210
+ };
211
+ }
212
+ return { hasExplicitBody: false };
213
+ }
214
+ function createReplayToken() {
215
+ return randomBytes(REPLAY_TOKEN_BYTES).toString("hex");
216
+ }
89
217
  /**
90
218
  * Create a Unix socket control server for daemon communication.
91
219
  */
92
220
  export function createControlServer(options) {
93
- const { socketPath, storage, proxyPort, version, projectRoot, logLevel, interceptorLoader, interceptorEventLog, } = options;
221
+ const { socketPath, storage, proxyPort, version, projectRoot, logLevel, interceptorLoader, interceptorEventLog, replayTracker, caCertPem, } = options;
94
222
  // Create logger if projectRoot is provided
95
223
  const logger = projectRoot
96
224
  ? createLogger("control", projectRoot, logLevel)
@@ -101,7 +229,7 @@ export function createControlServer(options) {
101
229
  }
102
230
  const handlers = {
103
231
  status: () => {
104
- const sessions = storage.listSessions();
232
+ const sessionCount = storage.countSessions();
105
233
  const requestCount = storage.countRequests();
106
234
  const interceptorCount = interceptorLoader
107
235
  ? interceptorLoader.getInterceptors().length
@@ -109,7 +237,7 @@ export function createControlServer(options) {
109
237
  return {
110
238
  running: true,
111
239
  proxyPort,
112
- sessionCount: sessions.length,
240
+ sessionCount,
113
241
  requestCount,
114
242
  version,
115
243
  interceptorCount,
@@ -162,8 +290,10 @@ export function createControlServer(options) {
162
290
  },
163
291
  searchBodies: (params) => {
164
292
  const query = requireString(params, "query");
293
+ const target = optionalBodySearchTarget(params, "target");
165
294
  return storage.searchBodies({
166
295
  query,
296
+ target,
167
297
  limit: optionalNumber(params, "limit"),
168
298
  offset: optionalNumber(params, "offset"),
169
299
  filter: optionalFilter(params),
@@ -171,7 +301,7 @@ export function createControlServer(options) {
171
301
  },
172
302
  queryJsonBodies: (params) => {
173
303
  const jsonPath = requireString(params, "jsonPath");
174
- const target = optionalString(params, "target");
304
+ const target = optionalQueryTarget(params, "target");
175
305
  return storage.queryJsonBodies({
176
306
  jsonPath,
177
307
  value: optionalString(params, "value"),
@@ -185,6 +315,69 @@ export function createControlServer(options) {
185
315
  storage.clearRequests();
186
316
  return { success: true };
187
317
  },
318
+ replayRequest: async (params) => {
319
+ if (!replayTracker) {
320
+ throw new Error("Replay is not available: replay tracker not initialised");
321
+ }
322
+ const id = requireString(params, "id");
323
+ const original = storage.getRequest(id);
324
+ if (!original) {
325
+ throw new Error(`Request not found: ${id}`);
326
+ }
327
+ const method = optionalString(params, "method") ?? original.method;
328
+ const url = optionalString(params, "url") ?? original.url;
329
+ const setHeaders = optionalStringRecord(params, "setHeaders");
330
+ const removeHeaders = optionalStringArray(params, "removeHeaders");
331
+ const timeoutMs = optionalNumber(params, "timeoutMs");
332
+ const replayInitiator = optionalReplayInitiator(params, "initiator") ?? "mcp";
333
+ try {
334
+ // Validate URL early for clearer errors.
335
+ new URL(url);
336
+ }
337
+ catch {
338
+ throw new Error(`Invalid URL for replay: ${url}`);
339
+ }
340
+ const replayBody = parseOptionalReplayBody(params);
341
+ const body = replayBody.hasExplicitBody ? replayBody.body : original.requestBody;
342
+ const replayToken = createReplayToken();
343
+ replayTracker.register(replayToken, original.id, replayInitiator);
344
+ const headers = buildReplayHeaders({
345
+ baseHeaders: original.requestHeaders,
346
+ setHeaders,
347
+ removeHeaders,
348
+ replayToken,
349
+ });
350
+ try {
351
+ const replayStart = Date.now();
352
+ await replayViaProxy({
353
+ proxyPort,
354
+ method,
355
+ url,
356
+ headers,
357
+ body,
358
+ timeoutMs,
359
+ caCertPem,
360
+ });
361
+ const recent = storage.listRequestsSummary({ limit: 50 });
362
+ const replayed = recent.find((request) => request.replayedFromId === original.id &&
363
+ request.replayInitiator === replayInitiator &&
364
+ request.timestamp >= replayStart);
365
+ if (!replayed) {
366
+ throw new Error("Replay request completed but could not locate captured replay result");
367
+ }
368
+ return { requestId: replayed.id };
369
+ }
370
+ catch (err) {
371
+ logger?.error("Replay request failed", {
372
+ originalRequestId: id,
373
+ method,
374
+ url,
375
+ replayInitiator,
376
+ error: err instanceof Error ? err.message : "Unknown error",
377
+ });
378
+ throw err;
379
+ }
380
+ },
188
381
  saveRequest: (params) => {
189
382
  const id = requireString(params, "id");
190
383
  const success = storage.bookmarkRequest(id);
@@ -229,9 +422,9 @@ export function createControlServer(options) {
229
422
  }
230
423
  const afterSeq = optionalNumber(params, "afterSeq") ?? 0;
231
424
  const limit = optionalNumber(params, "limit");
232
- const level = optionalString(params, "level");
425
+ const level = optionalInterceptorEventLevel(params, "level");
233
426
  const interceptor = optionalString(params, "interceptor");
234
- const type = optionalString(params, "type");
427
+ const type = optionalInterceptorEventType(params, "type");
235
428
  const events = afterSeq > 0
236
429
  ? interceptorEventLog.since(afterSeq, { limit, level, interceptor, type })
237
430
  : interceptorEventLog.latest(limit ?? 100);