ssh-mcp-pro 1.5.0 → 1.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 (91) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/MIGRATION.md +1 -1
  3. package/README.md +1 -1
  4. package/dist/agent-bin.js +2 -2
  5. package/dist/agent-bin.js.map +1 -1
  6. package/dist/mcp.d.ts +1 -1
  7. package/dist/mcp.js +1 -1
  8. package/dist/remote/action-record-sanitize.d.ts +4 -0
  9. package/dist/remote/action-record-sanitize.d.ts.map +1 -0
  10. package/dist/remote/action-record-sanitize.js +11 -0
  11. package/dist/remote/action-record-sanitize.js.map +1 -0
  12. package/dist/remote/agent-bounded-io.d.ts +10 -0
  13. package/dist/remote/agent-bounded-io.d.ts.map +1 -1
  14. package/dist/remote/agent-bounded-io.js +34 -0
  15. package/dist/remote/agent-bounded-io.js.map +1 -1
  16. package/dist/remote/agent-browser-enrollment.d.ts +16 -0
  17. package/dist/remote/agent-browser-enrollment.d.ts.map +1 -0
  18. package/dist/remote/agent-browser-enrollment.js +190 -0
  19. package/dist/remote/agent-browser-enrollment.js.map +1 -0
  20. package/dist/remote/agent-cli.d.ts +5 -0
  21. package/dist/remote/agent-cli.d.ts.map +1 -1
  22. package/dist/remote/agent-cli.js +91 -14
  23. package/dist/remote/agent-cli.js.map +1 -1
  24. package/dist/remote/agent-content-search.d.ts +20 -0
  25. package/dist/remote/agent-content-search.d.ts.map +1 -0
  26. package/dist/remote/agent-content-search.js +61 -0
  27. package/dist/remote/agent-content-search.js.map +1 -0
  28. package/dist/remote/agent-executor.d.ts +5 -0
  29. package/dist/remote/agent-executor.d.ts.map +1 -1
  30. package/dist/remote/agent-executor.js +98 -10
  31. package/dist/remote/agent-executor.js.map +1 -1
  32. package/dist/remote/agent-file-search.d.ts +22 -0
  33. package/dist/remote/agent-file-search.d.ts.map +1 -0
  34. package/dist/remote/agent-file-search.js +243 -0
  35. package/dist/remote/agent-file-search.js.map +1 -0
  36. package/dist/remote/agent-managed-process.d.ts +43 -0
  37. package/dist/remote/agent-managed-process.d.ts.map +1 -0
  38. package/dist/remote/agent-managed-process.js +291 -0
  39. package/dist/remote/agent-managed-process.js.map +1 -0
  40. package/dist/remote/control-plane.d.ts.map +1 -1
  41. package/dist/remote/control-plane.js +2 -1
  42. package/dist/remote/control-plane.js.map +1 -1
  43. package/dist/remote/dashboard-enrollment-commands.d.ts +1 -0
  44. package/dist/remote/dashboard-enrollment-commands.d.ts.map +1 -1
  45. package/dist/remote/dashboard-enrollment-commands.js +8 -0
  46. package/dist/remote/dashboard-enrollment-commands.js.map +1 -1
  47. package/dist/remote/dashboard-enrollment-feedback-script.d.ts +3 -0
  48. package/dist/remote/dashboard-enrollment-feedback-script.d.ts.map +1 -0
  49. package/dist/remote/dashboard-enrollment-feedback-script.js +227 -0
  50. package/dist/remote/dashboard-enrollment-feedback-script.js.map +1 -0
  51. package/dist/remote/mcp-tools.d.ts +1 -0
  52. package/dist/remote/mcp-tools.d.ts.map +1 -1
  53. package/dist/remote/mcp-tools.js +207 -3
  54. package/dist/remote/mcp-tools.js.map +1 -1
  55. package/dist/remote/safe-file.d.ts +19 -1
  56. package/dist/remote/safe-file.d.ts.map +1 -1
  57. package/dist/remote/safe-file.js +77 -3
  58. package/dist/remote/safe-file.js.map +1 -1
  59. package/dist/remote/schemas.d.ts +16 -0
  60. package/dist/remote/schemas.d.ts.map +1 -1
  61. package/dist/remote/schemas.js +1 -0
  62. package/dist/remote/schemas.js.map +1 -1
  63. package/dist/remote/store.d.ts +2 -3
  64. package/dist/remote/store.d.ts.map +1 -1
  65. package/dist/remote/store.js +15 -3
  66. package/dist/remote/store.js.map +1 -1
  67. package/dist/remote/types.d.ts +3 -2
  68. package/dist/remote/types.d.ts.map +1 -1
  69. package/dist/remote/types.js +15 -0
  70. package/dist/remote/types.js.map +1 -1
  71. package/dist/remote/web-dashboard-html.d.ts.map +1 -1
  72. package/dist/remote/web-dashboard-html.js +25 -27
  73. package/dist/remote/web-dashboard-html.js.map +1 -1
  74. package/dist/remote/web-dashboard-styles.d.ts +1 -1
  75. package/dist/remote/web-dashboard-styles.d.ts.map +1 -1
  76. package/dist/remote/web-dashboard-styles.js +2 -0
  77. package/dist/remote/web-dashboard-styles.js.map +1 -1
  78. package/dist/remote/web-dashboard.d.ts.map +1 -1
  79. package/dist/remote/web-dashboard.js +9 -2
  80. package/dist/remote/web-dashboard.js.map +1 -1
  81. package/dist/types.d.ts +1 -1
  82. package/docs/adding-a-device.md +187 -11
  83. package/docs/backup-restore-verification.md +45 -0
  84. package/docs/coolify.md +9 -0
  85. package/docs/development/local-setup.md +1 -1
  86. package/docs/reference/compatibility.md +1 -1
  87. package/docs/tutorials/getting-started.md +1 -1
  88. package/mcp.json +1 -1
  89. package/package.json +2 -2
  90. package/registry/ssh-mcp-pro/mcp.json +1 -1
  91. package/server.json +3 -3
package/CHANGELOG.md CHANGED
@@ -5,6 +5,40 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.6.0](https://github.com/oaslananka/ssh-mcp-pro/compare/ssh-mcp-pro-v1.5.0...ssh-mcp-pro-v1.6.0) (2026-11-12)
9
+
10
+
11
+ ### Features
12
+
13
+ * **agent:** approve Linux enrollment in local browser ([#239](https://github.com/oaslananka/ssh-mcp-pro/issues/239)) ([7ba75d4](https://github.com/oaslananka/ssh-mcp-pro/commit/7ba75d42a19a07ae2de18a7872d2fa54eb0415b8))
14
+ * **agent:** install Node LTS on first Windows enrollment ([#219](https://github.com/oaslananka/ssh-mcp-pro/issues/219)) ([8d2f82f](https://github.com/oaslananka/ssh-mcp-pro/commit/8d2f82f8696751bcf469fd42245327d5ed4007e7))
15
+ * **dashboard:** confirm first device is Live Online during setup ([#227](https://github.com/oaslananka/ssh-mcp-pro/issues/227)) ([7447971](https://github.com/oaslananka/ssh-mcp-pro/commit/74479716133c8d34f7233d681d58d3881b3e5f47))
16
+ * **install:** verified Node LTS bootstrap for clean Linux and WSL ([#237](https://github.com/oaslananka/ssh-mcp-pro/issues/237)) ([600b6f3](https://github.com/oaslananka/ssh-mcp-pro/commit/600b6f3f67ffd522817719be70c7839cb864d59e))
17
+ * **recovery:** verify encrypted backup restore offline ([#233](https://github.com/oaslananka/ssh-mcp-pro/issues/233)) ([4187749](https://github.com/oaslananka/ssh-mcp-pro/commit/41877496c8233c1c02958fc1579ba96d0745fb7c))
18
+ * **remote:** discover allowlisted files by name ([#217](https://github.com/oaslananka/ssh-mcp-pro/issues/217)) ([0f04209](https://github.com/oaslananka/ssh-mcp-pro/commit/0f04209464fd83b2d34ca8713b244969c8aeba80))
19
+ * **remote:** manage interactive agent processes with bounded output ([#221](https://github.com/oaslananka/ssh-mcp-pro/issues/221)) ([1beaddb](https://github.com/oaslananka/ssh-mcp-pro/commit/1beaddb1ff96f3d2bcbeb407322b0a65aa87f218))
20
+ * **remote:** page bounded file reads with UTF-8 safe cursors ([#215](https://github.com/oaslananka/ssh-mcp-pro/issues/215)) ([cce420b](https://github.com/oaslananka/ssh-mcp-pro/commit/cce420bcda302289fc92638e778f42848218cad1))
21
+ * **remote:** preview and apply guarded file text replacements ([#220](https://github.com/oaslananka/ssh-mcp-pro/issues/220)) ([eb3a837](https://github.com/oaslananka/ssh-mcp-pro/commit/eb3a8374942c1fe939a71f1abf3ccd130e0d9d48))
22
+ * **remote:** search allowed file contents by literal phrase ([#223](https://github.com/oaslananka/ssh-mcp-pro/issues/223)) ([f82034d](https://github.com/oaslananka/ssh-mcp-pro/commit/f82034d1c2cbad1706effc398c8666b48f9c1df7))
23
+
24
+
25
+ ### Bug Fixes
26
+
27
+ * **agent:** make Windows enrollment work with restricted PowerShell ([#218](https://github.com/oaslananka/ssh-mcp-pro/issues/218)) ([de97766](https://github.com/oaslananka/ssh-mcp-pro/commit/de977665266ea7437ad885134d37b2ccb2f69025))
28
+ * **agent:** reconnect safely after transient remote socket loss ([#230](https://github.com/oaslananka/ssh-mcp-pro/issues/230)) ([1f79e6e](https://github.com/oaslananka/ssh-mcp-pro/commit/1f79e6ec3a915eef66668c2dda90e7afe9be706b))
29
+ * **agent:** remove Windows scheduled task runtime cap ([#229](https://github.com/oaslananka/ssh-mcp-pro/issues/229)) ([e0956bf](https://github.com/oaslananka/ssh-mcp-pro/commit/e0956bf084b4133b278ee5d3aaf8b315f95d3b92))
30
+ * **agent:** report failure to persist Linux user service ([#224](https://github.com/oaslananka/ssh-mcp-pro/issues/224)) ([5976050](https://github.com/oaslananka/ssh-mcp-pro/commit/59760503acc5576c49bb7d48cea9220acff5f291))
31
+ * **agent:** report Linux status without uptime utility ([#231](https://github.com/oaslananka/ssh-mcp-pro/issues/231)) ([8868741](https://github.com/oaslananka/ssh-mcp-pro/commit/8868741439407a3dbe6d10cb57a3b06efdfd865e))
32
+ * **dashboard:** allow browser enrollment callback to loopback ([#242](https://github.com/oaslananka/ssh-mcp-pro/issues/242)) ([7ccf4d1](https://github.com/oaslananka/ssh-mcp-pro/commit/7ccf4d13485ca99aaff9142cf0a50f9fb656c8d1))
33
+ * **dashboard:** explain provider-scoped device inventory ([#234](https://github.com/oaslananka/ssh-mcp-pro/issues/234)) ([ac5a10c](https://github.com/oaslananka/ssh-mcp-pro/commit/ac5a10c10ee81aae54e8c1dfae03d61e66639d6a))
34
+ * **dashboard:** show tenant-scoped device names in audit history ([#232](https://github.com/oaslananka/ssh-mcp-pro/issues/232)) ([ef0b8eb](https://github.com/oaslananka/ssh-mcp-pro/commit/ef0b8eb9398216cc88b0deaa4c8de80e58f77665))
35
+ * **docker:** bundle installer scripts in standard runtime image ([#226](https://github.com/oaslananka/ssh-mcp-pro/issues/226)) ([ba6fffe](https://github.com/oaslananka/ssh-mcp-pro/commit/ba6fffe78877da2b7abaf2676663f15ebde36e7b))
36
+ * **install:** clarify WSL agent lifetime after setup ([#241](https://github.com/oaslananka/ssh-mcp-pro/issues/241)) ([e1215b1](https://github.com/oaslananka/ssh-mcp-pro/commit/e1215b19d5f4870ccaf99c938f12b5525f5ee2d0))
37
+ * **install:** explain missing system Node and npm ([#240](https://github.com/oaslananka/ssh-mcp-pro/issues/240)) ([249ee4c](https://github.com/oaslananka/ssh-mcp-pro/commit/249ee4c8f4327bfaa20e90acb09ee6c66f8208cb))
38
+ * **remote:** describe mutation risks and output schemas ([#213](https://github.com/oaslananka/ssh-mcp-pro/issues/213)) ([c7d18f4](https://github.com/oaslananka/ssh-mcp-pro/commit/c7d18f4bcb0f9c69537277db23ce971ad051f9ec))
39
+ * **remote:** expire abandoned pending agents ([#225](https://github.com/oaslananka/ssh-mcp-pro/issues/225)) ([43a730b](https://github.com/oaslananka/ssh-mcp-pro/commit/43a730b543ce8d8d67111dfe0391a39c57aacee4))
40
+ * **windows:** provide tokenless legacy agent task repair ([#235](https://github.com/oaslananka/ssh-mcp-pro/issues/235)) ([5a267e8](https://github.com/oaslananka/ssh-mcp-pro/commit/5a267e8cb60a4f25b3cb0fea2911439b8774d2c5))
41
+
8
42
  ## [1.5.0](https://github.com/oaslananka/ssh-mcp-pro/compare/ssh-mcp-pro-v1.4.3...ssh-mcp-pro-v1.5.0) (2026-11-10)
9
43
 
10
44
 
package/MIGRATION.md CHANGED
@@ -7,7 +7,7 @@ Version `1.0.0` is the initial public baseline for this repository. There are no
7
7
  When upgrading within `1.x`, preserve these compatibility expectations:
8
8
 
9
9
  - Node.js must satisfy `>=22.22.2` (Node 24 and 26 are tested in CI).
10
- - pnpm must satisfy `^11.28.10` for current development and build tooling.
10
+ - pnpm must satisfy `^11.28.11` for current development and build tooling.
11
11
  - Existing stdio MCP client configs can continue to launch `ssh-mcp-pro` with no arguments.
12
12
  - HTTP deployments should keep explicit authentication, allowed origins, host allowlists, strict host-key verification, and a remote-safe tool profile.
13
13
 
package/README.md CHANGED
@@ -38,7 +38,7 @@ ssh-mcp-pro is a secure Model Context Protocol (MCP) server for SSH automation.
38
38
  ## Prerequisites
39
39
 
40
40
  - Node.js `>=22.22.2` or `>=24.15.0` or `>=26.3.0`
41
- - pnpm `^11.28.10` (use Corepack for development)
41
+ - pnpm `^11.28.11` (use Corepack for development)
42
42
  - SSH access to the target hosts
43
43
  - Docker, only for local integration tests and container image builds
44
44
 
package/dist/agent-bin.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
- import { runAgentCli } from "./remote/agent-cli.js";
2
+ import { runAgentCliWithReconnect } from "./remote/agent-cli.js";
3
3
  try {
4
- await runAgentCli(process.argv.slice(2));
4
+ await runAgentCliWithReconnect(process.argv.slice(2));
5
5
  }
6
6
  catch (error) {
7
7
  const message = error instanceof Error ? error.message : String(error);
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bin.js","sourceRoot":"","sources":["../src/agent-bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAEpD,IAAI,CAAC;IACH,MAAM,WAAW,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AAC3C,CAAC;AAAC,OAAO,KAAc,EAAE,CAAC;IACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
1
+ {"version":3,"file":"agent-bin.js","sourceRoot":"","sources":["../src/agent-bin.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,wBAAwB,EAAE,MAAM,uBAAuB,CAAC;AAEjE,IAAI,CAAC;IACH,MAAM,wBAAwB,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;AACxD,CAAC;AAAC,OAAO,KAAc,EAAE,CAAC;IACxB,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,OAAO,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC"}
package/dist/mcp.d.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { McpServer } from "@modelcontextprotocol/server";
2
2
  import type { Transport } from "@modelcontextprotocol/server";
3
3
  import type { AppContainer } from "./container.js";
4
- export declare const SERVER_VERSION = "1.5.0";
4
+ export declare const SERVER_VERSION = "1.6.0";
5
5
  export declare const SERVER_NAME = "io.github.oaslananka/ssh-mcp-pro";
6
6
  export declare class SSHMCPServer {
7
7
  private readonly container;
package/dist/mcp.js CHANGED
@@ -5,7 +5,7 @@ import { listResources, readResource } from "./resources.js";
5
5
  import { getMCPPrompt, listMCPPrompts } from "./prompts.js";
6
6
  import { withSpan } from "./telemetry.js";
7
7
  import { createToolRegistry } from "./tools/index.js";
8
- export const SERVER_VERSION = "1.5.0"; // x-release-please-version
8
+ export const SERVER_VERSION = "1.6.0"; // x-release-please-version
9
9
  export const SERVER_NAME = "io.github.oaslananka/ssh-mcp-pro";
10
10
  function getSessionIdFromArgs(args) {
11
11
  if (typeof args !== "object" || args === null || Array.isArray(args)) {
@@ -0,0 +1,4 @@
1
+ import type { RemoteToolName } from "./types.js";
2
+ /** Keep managed process command and stdin payloads out of durable action metadata. */
3
+ export declare function safeStoredActionArgs(tool: RemoteToolName, args: Record<string, unknown>): Record<string, unknown>;
4
+ //# sourceMappingURL=action-record-sanitize.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-record-sanitize.d.ts","sourceRoot":"","sources":["../../src/remote/action-record-sanitize.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAEjD,sFAAsF;AACtF,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC5B,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAKzB"}
@@ -0,0 +1,11 @@
1
+ /** Keep managed process command and stdin payloads out of durable action metadata. */
2
+ export function safeStoredActionArgs(tool, args) {
3
+ if (tool === "process_start")
4
+ return { ...args, command: "[redacted]" };
5
+ if (tool === "process_input")
6
+ return { ...args, input: "[redacted]" };
7
+ if (tool === "file_content_search")
8
+ return { ...args, query: "[redacted]" };
9
+ return args;
10
+ }
11
+ //# sourceMappingURL=action-record-sanitize.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"action-record-sanitize.js","sourceRoot":"","sources":["../../src/remote/action-record-sanitize.ts"],"names":[],"mappings":"AAEA,sFAAsF;AACtF,MAAM,UAAU,oBAAoB,CAClC,IAAoB,EACpB,IAA6B;IAE7B,IAAI,IAAI,KAAK,eAAe;QAAE,OAAO,EAAE,GAAG,IAAI,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC;IACxE,IAAI,IAAI,KAAK,eAAe;QAAE,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IACtE,IAAI,IAAI,KAAK,qBAAqB;QAAE,OAAO,EAAE,GAAG,IAAI,EAAE,KAAK,EAAE,YAAY,EAAE,CAAC;IAC5E,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -13,6 +13,16 @@ export declare function readFileBounded(file: FileHandle, maxBytes: number): Pro
13
13
  value: Buffer;
14
14
  truncated: boolean;
15
15
  }>;
16
+ /**
17
+ * Read one page using byte offsets without buffering the entire file.
18
+ * Unlike a legacy read, a page must not include a truncation marker in text.
19
+ * Valid UTF-8 is kept at code-point boundaries when a page ends mid-character.
20
+ */
21
+ export declare function readFilePageBounded(file: FileHandle, offsetBytes: number, maxBytes: number): Promise<{
22
+ value: Buffer;
23
+ truncated: boolean;
24
+ nextOffsetBytes?: number;
25
+ }>;
16
26
  export declare function readFileTailBounded(file: FileHandle, maxBytes: number, maxLines?: number): Promise<{
17
27
  value: Buffer;
18
28
  truncated: boolean;
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bounded-io.d.ts","sourceRoot":"","sources":["../../src/remote/agent-bounded-io.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AAkCD,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,IAAI,CAcN;AAED,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,iBAAiB,EAC9B,QAAQ,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAMvC;AAoBD,wBAAsB,eAAe,CACnC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAID;AAyCD,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,SAAM,GACb,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAcD"}
1
+ {"version":3,"file":"agent-bounded-io.d.ts","sourceRoot":"","sources":["../../src/remote/agent-bounded-io.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAEnD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,EAAE,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;CACpB;AAkCD,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,iBAAiB,EAC9B,KAAK,EAAE,MAAM,EACb,QAAQ,EAAE,MAAM,GACf,IAAI,CAcN;AAED,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,iBAAiB,EAC9B,QAAQ,EAAE,MAAM,GACf;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAMvC;AAoBD,wBAAsB,eAAe,CACnC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAID;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,UAAU,EAChB,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAC;IAAC,eAAe,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAgC1E;AAyCD,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,UAAU,EAChB,QAAQ,EAAE,MAAM,EAChB,QAAQ,SAAM,GACb,OAAO,CAAC;IACT,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,CAAC;CACpB,CAAC,CAcD"}
@@ -60,6 +60,40 @@ export async function readFileBounded(file, maxBytes) {
60
60
  const buffer = await readHandleRange(file, 0, cappedBytes + 1);
61
61
  return truncateBuffer(buffer, cappedBytes);
62
62
  }
63
+ /**
64
+ * Read one page using byte offsets without buffering the entire file.
65
+ * Unlike a legacy read, a page must not include a truncation marker in text.
66
+ * Valid UTF-8 is kept at code-point boundaries when a page ends mid-character.
67
+ */
68
+ export async function readFilePageBounded(file, offsetBytes, maxBytes) {
69
+ const raw = await readHandleRange(file, offsetBytes, maxBytes + 1);
70
+ let page = raw.subarray(0, maxBytes);
71
+ let truncated = raw.byteLength > maxBytes;
72
+ const decoder = new TextDecoder("utf-8", { fatal: true });
73
+ let validPage = false;
74
+ for (let removed = 0; removed <= (truncated ? Math.min(3, page.byteLength) : 0); removed += 1) {
75
+ try {
76
+ decoder.decode(page.subarray(0, page.byteLength - removed));
77
+ page = page.subarray(0, page.byteLength - removed);
78
+ validPage = true;
79
+ break;
80
+ }
81
+ catch {
82
+ // A UTF-8 code point may cross the page boundary.
83
+ }
84
+ }
85
+ if (!validPage || (truncated && page.byteLength === 0)) {
86
+ throw Object.assign(new Error("Page cannot advance at this UTF-8 boundary; increase max_bytes or use a valid offset"), {
87
+ code: "POLICY_DENIED",
88
+ });
89
+ }
90
+ truncated ||= page.byteLength < raw.byteLength;
91
+ return {
92
+ value: page,
93
+ truncated,
94
+ ...(truncated && page.byteLength > 0 ? { nextOffsetBytes: offsetBytes + page.byteLength } : {}),
95
+ };
96
+ }
63
97
  const TAIL_SCAN_CHUNK_BYTES = 64 * 1024;
64
98
  const MAX_TAIL_SCAN_BYTES = 8 * 1024 * 1024;
65
99
  async function findTailStart(file, size, maxLines, maxScanBytes) {
@@ -1 +1 @@
1
- {"version":3,"file":"agent-bounded-io.js","sourceRoot":"","sources":["../../src/remote/agent-bounded-io.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"agent-bounded-io.js","sourceRoot":"","sources":["../../src/remote/agent-bounded-io.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface LocalBrowserEnrollment {
2
+ verificationUrl: string;
3
+ callbackUrl: string;
4
+ port: number;
5
+ finished: Promise<void>;
6
+ close(): void;
7
+ }
8
+ /**
9
+ * A same-device HTTPS dashboard may hand its one-time token directly to a
10
+ * loopback listener. Only the bound browser origin, random challenge and
11
+ * local socket are accepted; the one-time token never appears in the URL.
12
+ */
13
+ export declare function startLocalBrowserEnrollment(serverUrl: string, alias: string, onToken: (token: string) => Promise<void>, options?: {
14
+ timeoutMs?: number;
15
+ }): Promise<LocalBrowserEnrollment>;
16
+ //# sourceMappingURL=agent-browser-enrollment.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-browser-enrollment.d.ts","sourceRoot":"","sources":["../../src/remote/agent-browser-enrollment.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,sBAAsB;IACrC,eAAe,EAAE,MAAM,CAAC;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,OAAO,CAAC,IAAI,CAAC,CAAC;IACxB,KAAK,IAAI,IAAI,CAAC;CACf;AA8DD;;;;GAIG;AACH,wBAAsB,2BAA2B,CAC/C,SAAS,EAAE,MAAM,EACjB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,EACzC,OAAO,GAAE;IAAE,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GACnC,OAAO,CAAC,sBAAsB,CAAC,CAkIjC"}
@@ -0,0 +1,190 @@
1
+ import { randomBytes, timingSafeEqual } from "node:crypto";
2
+ import { createServer } from "node:http";
3
+ const MAX_BODY_BYTES = 4_096;
4
+ const DEFAULT_TIMEOUT_MS = 600_000;
5
+ function safeEqual(expected, actual) {
6
+ const left = Buffer.from(expected);
7
+ const right = Buffer.from(actual);
8
+ return left.length === right.length && timingSafeEqual(left, right);
9
+ }
10
+ function validServerOrigin(serverUrl) {
11
+ const url = new URL(serverUrl);
12
+ if (url.username ||
13
+ url.password ||
14
+ url.search ||
15
+ url.hash ||
16
+ (url.protocol !== "https:" &&
17
+ !(url.protocol === "http:" && ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname)))) {
18
+ throw new Error("Browser enrollment requires an HTTPS server or local loopback URL");
19
+ }
20
+ return url.origin;
21
+ }
22
+ function send(res, code, message, origin, preflight = false) {
23
+ res.writeHead(code, {
24
+ "Content-Type": "text/plain; charset=utf-8",
25
+ "Cache-Control": "no-store",
26
+ ...(origin
27
+ ? {
28
+ "Access-Control-Allow-Origin": origin,
29
+ Vary: "Origin",
30
+ ...(preflight
31
+ ? {
32
+ "Access-Control-Allow-Methods": "POST, OPTIONS",
33
+ "Access-Control-Allow-Headers": "content-type",
34
+ "Access-Control-Max-Age": "0",
35
+ }
36
+ : {}),
37
+ }
38
+ : {}),
39
+ });
40
+ res.end(message);
41
+ }
42
+ async function readLimitedJson(req) {
43
+ const chunks = [];
44
+ let size = 0;
45
+ for await (const chunk of req) {
46
+ const bytes = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
47
+ size += bytes.length;
48
+ if (size > MAX_BODY_BYTES)
49
+ throw new Error("Request body exceeds limit");
50
+ chunks.push(bytes);
51
+ }
52
+ return JSON.parse(Buffer.concat(chunks).toString("utf8"));
53
+ }
54
+ /**
55
+ * A same-device HTTPS dashboard may hand its one-time token directly to a
56
+ * loopback listener. Only the bound browser origin, random challenge and
57
+ * local socket are accepted; the one-time token never appears in the URL.
58
+ */
59
+ export async function startLocalBrowserEnrollment(serverUrl, alias, onToken, options = {}) {
60
+ const origin = validServerOrigin(serverUrl);
61
+ const state = randomBytes(32).toString("base64url");
62
+ let complete;
63
+ let fail;
64
+ let settled = false;
65
+ const finished = new Promise((resolve, reject) => {
66
+ complete = () => {
67
+ if (settled)
68
+ return;
69
+ settled = true;
70
+ resolve();
71
+ };
72
+ fail = (error) => {
73
+ if (settled)
74
+ return;
75
+ settled = true;
76
+ reject(error);
77
+ };
78
+ });
79
+ // Closing early is allowed without an unhandled rejection, while callers
80
+ // awaiting finished still receive the same timeout/cancellation failure.
81
+ void finished.catch(() => { });
82
+ let enrolled = false;
83
+ const listener = createServer((req, res) => {
84
+ void handleRequest(req, res).catch(() => {
85
+ if (!res.headersSent)
86
+ send(res, 500, "Local approval failed");
87
+ });
88
+ });
89
+ async function handleRequest(req, res) {
90
+ const remote = req.socket.remoteAddress ?? "";
91
+ const localOnly = remote === "127.0.0.1";
92
+ if (!localOnly || req.headers.origin !== origin) {
93
+ send(res, 403, "Browser origin is not allowed");
94
+ return;
95
+ }
96
+ const address = listener.address();
97
+ if (req.headers.host !== `127.0.0.1:${address.port}` ||
98
+ (req.url !== "/enroll" && req.url !== "/ready")) {
99
+ send(res, 403, "Unexpected callback destination", origin);
100
+ return;
101
+ }
102
+ if (req.method === "GET" && req.url === "/ready") {
103
+ send(res, 204, "", origin);
104
+ return;
105
+ }
106
+ if (req.method === "OPTIONS") {
107
+ send(res, 204, "", origin, true);
108
+ return;
109
+ }
110
+ if (req.method !== "POST" || enrolled) {
111
+ send(res, 405, "Enrollment is not available", origin);
112
+ return;
113
+ }
114
+ if (!(req.headers["content-type"] ?? "").startsWith("application/json")) {
115
+ send(res, 415, "JSON required", origin);
116
+ return;
117
+ }
118
+ let body;
119
+ try {
120
+ body = await readLimitedJson(req);
121
+ }
122
+ catch {
123
+ send(res, 400, "Invalid enrollment request", origin);
124
+ return;
125
+ }
126
+ if (!body || typeof body !== "object" || Array.isArray(body)) {
127
+ send(res, 400, "Invalid enrollment request", origin);
128
+ return;
129
+ }
130
+ const data = body;
131
+ if (typeof data.state !== "string" || !safeEqual(state, data.state)) {
132
+ send(res, 403, "Enrollment link does not match", origin);
133
+ return;
134
+ }
135
+ if (typeof data.token !== "string" || !/^[A-Za-z0-9_-]{30,256}$/u.test(data.token)) {
136
+ send(res, 400, "Enrollment token is missing or malformed", origin);
137
+ return;
138
+ }
139
+ enrolled = true;
140
+ try {
141
+ await onToken(data.token);
142
+ send(res, 200, "Enrollment accepted", origin);
143
+ complete();
144
+ listener.close();
145
+ }
146
+ catch {
147
+ enrolled = false;
148
+ send(res, 502, "Device enrollment failed; check the agent terminal", origin);
149
+ }
150
+ }
151
+ await new Promise((resolve, reject) => {
152
+ listener.once("error", reject);
153
+ listener.listen(0, "127.0.0.1", () => {
154
+ listener.off("error", reject);
155
+ resolve();
156
+ });
157
+ });
158
+ const address = listener.address();
159
+ const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
160
+ if (!Number.isFinite(timeoutMs) || timeoutMs <= 0 || timeoutMs > DEFAULT_TIMEOUT_MS) {
161
+ listener.close();
162
+ throw new Error("Invalid local browser enrollment timeout");
163
+ }
164
+ const timer = setTimeout(() => {
165
+ fail(new Error("Browser enrollment expired; restart the installer"));
166
+ listener.close();
167
+ }, timeoutMs);
168
+ timer.unref();
169
+ void finished
170
+ .finally(() => {
171
+ clearTimeout(timer);
172
+ })
173
+ .catch(() => { });
174
+ const dashboardUrl = new URL("/dashboard", origin);
175
+ dashboardUrl.searchParams.set("local_enroll_port", String(address.port));
176
+ dashboardUrl.searchParams.set("local_enroll_state", state);
177
+ dashboardUrl.searchParams.set("local_enroll_alias", alias);
178
+ return {
179
+ verificationUrl: dashboardUrl.toString(),
180
+ callbackUrl: `http://127.0.0.1:${address.port}/enroll`,
181
+ port: address.port,
182
+ finished,
183
+ close() {
184
+ fail(new Error("Browser enrollment cancelled"));
185
+ clearTimeout(timer);
186
+ listener.close();
187
+ },
188
+ };
189
+ }
190
+ //# sourceMappingURL=agent-browser-enrollment.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"agent-browser-enrollment.js","sourceRoot":"","sources":["../../src/remote/agent-browser-enrollment.ts"],"names":[],"mappings":"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"}
@@ -1,2 +1,7 @@
1
+ /** Recoverable network failure; invalid enrollment/configuration must not be retried. */
2
+ export declare class RetryableAgentTransportError extends Error {
3
+ }
1
4
  export declare function runAgentCli(argv: string[]): Promise<void>;
5
+ /** Keep installed agents online across transient socket failures without re-enrollment. */
6
+ export declare function runAgentCliWithReconnect(argv: string[], run?: (args: string[]) => Promise<void>, pause?: (delayMs: number) => Promise<void>): Promise<void>;
2
7
  //# sourceMappingURL=agent-cli.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"agent-cli.d.ts","sourceRoot":"","sources":["../../src/remote/agent-cli.ts"],"names":[],"mappings":"AAsfA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiC/D"}
1
+ {"version":3,"file":"agent-cli.d.ts","sourceRoot":"","sources":["../../src/remote/agent-cli.ts"],"names":[],"mappings":"AAwDA,yFAAyF;AACzF,qBAAa,4BAA6B,SAAQ,KAAK;CAAG;AAkf1D,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAiC/D;AACD,2FAA2F;AAC3F,wBAAgB,wBAAwB,CACtC,IAAI,EAAE,MAAM,EAAE,EACd,GAAG,GAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,OAAO,CAAC,IAAI,CAAe,EACpD,KAAK,GAAE,CAAC,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CACW,GAClD,OAAO,CAAC,IAAI,CAAC,CA2Bf"}
@@ -3,10 +3,14 @@ import os from "node:os";
3
3
  import path from "node:path";
4
4
  import { fileURLToPath } from "node:url";
5
5
  import { AgentExecutor, defaultHostMetadata } from "./agent-executor.js";
6
+ import { startLocalBrowserEnrollment } from "./agent-browser-enrollment.js";
6
7
  import { AgentActivityLog, agentActivityLogPath, showAgentActivityLogs, } from "./agent-activity-log.js";
7
8
  import { ensurePemKeyPair, nowIso, randomToken, signEnvelope, verifyEnvelope, } from "./crypto.js";
8
9
  import { rememberAcceptedPolicyUpdate, validatePolicyUpdate, } from "./policy-update-guard.js";
9
10
  import { parseControlPlaneEnvelope, parseAgentPolicy } from "./schemas.js";
11
+ /** Recoverable network failure; invalid enrollment/configuration must not be retried. */
12
+ export class RetryableAgentTransportError extends Error {
13
+ }
10
14
  function output(line) {
11
15
  process.stdout.write(`${line}\n`);
12
16
  }
@@ -123,13 +127,7 @@ function removeTrailingSlashes(value) {
123
127
  }
124
128
  return value.slice(0, end);
125
129
  }
126
- async function enroll(argv) {
127
- const server = removeTrailingSlashes(parseFlag(argv, "--server") ?? "");
128
- const token = parseFlag(argv, "--token");
129
- const alias = parseFlag(argv, "--alias") ?? os.hostname();
130
- if (!server || !token) {
131
- throw new Error("Usage: ssh-mcp-pro-agent enroll --server <url> --token <token> --alias <alias>");
132
- }
130
+ async function enrollWithToken(server, token, alias) {
133
131
  const keyPair = ensurePemKeyPair(keyPath());
134
132
  const response = await postJson(`${server}/api/agents/enroll`, {
135
133
  token,
@@ -153,6 +151,31 @@ async function enroll(argv) {
153
151
  output(`Agent enrolled: ${config.agentId} (${config.alias})`);
154
152
  output(`Config: ${configPath()}`);
155
153
  }
154
+ async function enroll(argv) {
155
+ const server = removeTrailingSlashes(parseFlag(argv, "--server") ?? "");
156
+ const token = parseFlag(argv, "--token");
157
+ const browser = argv.includes("--browser");
158
+ const alias = parseFlag(argv, "--alias") ?? os.hostname();
159
+ if (!server || (!token && !browser) || (token !== undefined && browser)) {
160
+ throw new Error("Usage: ssh-mcp-pro-agent enroll --server <url> --token <token> --alias <alias> (or --browser without --token)");
161
+ }
162
+ if (!browser) {
163
+ await enrollWithToken(server, token, alias);
164
+ return;
165
+ }
166
+ const local = await startLocalBrowserEnrollment(server, alias, async (oneTimeToken) => {
167
+ await enrollWithToken(server, oneTimeToken, alias);
168
+ });
169
+ output("Open this URL in a browser on this same device to approve read-only or selected device access:");
170
+ output(local.verificationUrl);
171
+ output("Waiting for browser approval; the enrollment token stays on this device (10-minute limit).");
172
+ try {
173
+ await local.finished;
174
+ }
175
+ finally {
176
+ local.close();
177
+ }
178
+ }
156
179
  /** A server-side ready acknowledgement is distinct from an open WebSocket. */
157
180
  function handleAgentControlMessage(payload, config, activity) {
158
181
  if (payload.type === "agent.ready") {
@@ -299,16 +322,44 @@ async function runAgent() {
299
322
  activity.event("error", "action_failed", "Action processing failed; details withheld.");
300
323
  });
301
324
  };
325
+ // An error can precede the close event carrying a non-retryable policy code.
326
+ let errorCloseTimer;
327
+ let closed = false;
302
328
  ws.onerror = () => {
303
- activity.event("error", "socket_error", "Connection error. Service restart may be required.");
304
- reject(new Error("WebSocket transport error"));
329
+ if (closed)
330
+ return;
331
+ activity.event("error", "socket_error", "Connection error. Checking close reason.");
332
+ errorCloseTimer ??= setTimeout(() => {
333
+ errorCloseTimer = undefined;
334
+ closed = true;
335
+ reject(new RetryableAgentTransportError("WebSocket transport error"));
336
+ // If a runtime never delivers close after error, do not retain a stale socket.
337
+ try {
338
+ ws.close();
339
+ }
340
+ catch {
341
+ // The transport may already be unusable.
342
+ }
343
+ }, 2_000);
305
344
  };
306
- // A disconnected agent must exit nonzero so its service supervisor can restart it.
307
- ws.onclose = () => {
308
- activity.event("warn", "disconnected", "Connection closed. Awaiting service restart.");
309
- reject(new Error("Agent WebSocket connection closed"));
345
+ // Policy-denied agents must not loop; ordinary transport losses are recoverable.
346
+ ws.onclose = (event) => {
347
+ if (closed)
348
+ return;
349
+ closed = true;
350
+ if (errorCloseTimer) {
351
+ clearTimeout(errorCloseTimer);
352
+ errorCloseTimer = undefined;
353
+ }
354
+ if (event?.code === 1008) {
355
+ activity.event("error", "connection_denied", "Server rejected the agent connection.");
356
+ reject(new Error("Agent connection rejected by server"));
357
+ return;
358
+ }
359
+ activity.event("warn", "disconnected", "Connection closed. Reconnecting automatically.");
360
+ reject(new RetryableAgentTransportError("Agent WebSocket connection closed"));
310
361
  };
311
- });
362
+ }).finally(() => executor.dispose());
312
363
  }
313
364
  function status() {
314
365
  if (!existsSync(configPath())) {
@@ -357,6 +408,7 @@ function uninstallService() {
357
408
  function printAgentUsage() {
358
409
  output("Usage:");
359
410
  output(" ssh-mcp-pro-agent enroll --server <url> --token <token> --alias <alias>");
411
+ output(" ssh-mcp-pro-agent enroll --server <url> --browser --alias <alias>");
360
412
  output(" ssh-mcp-pro-agent run");
361
413
  output(" ssh-mcp-pro-agent status");
362
414
  output(" ssh-mcp-pro-agent logs [--tail 60] [--follow]");
@@ -406,4 +458,29 @@ export async function runAgentCli(argv) {
406
458
  throw new Error("Unknown agent command. Run ssh-mcp-pro-agent --help for usage.");
407
459
  }
408
460
  }
461
+ /** Keep installed agents online across transient socket failures without re-enrollment. */
462
+ export function runAgentCliWithReconnect(argv, run = runAgentCli, pause = (ms) => new Promise((resolve) => setTimeout(resolve, ms))) {
463
+ let delayMs = 1_000;
464
+ // Schedule each attempt independently so long-lived agents do not retain retry stacks.
465
+ return new Promise((resolve, reject) => {
466
+ function handleFailure(error) {
467
+ if (argv[0] !== "run" || !(error instanceof RetryableAgentTransportError)) {
468
+ reject(error);
469
+ return;
470
+ }
471
+ const currentDelayMs = delayMs;
472
+ delayMs = Math.min(delayMs * 2, 30_000);
473
+ process.stderr.write(`Agent connection lost; retrying in ${currentDelayMs / 1_000} seconds.\n`);
474
+ void Promise.resolve()
475
+ .then(() => pause(currentDelayMs))
476
+ .then(attempt, reject);
477
+ }
478
+ function attempt() {
479
+ void Promise.resolve()
480
+ .then(() => run(argv))
481
+ .then(resolve, handleFailure);
482
+ }
483
+ attempt();
484
+ });
485
+ }
409
486
  //# sourceMappingURL=agent-cli.js.map