host-execution-mcp 1.0.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 (126) hide show
  1. package/CHANGELOG.md +330 -0
  2. package/LICENSE +21 -0
  3. package/README.md +92 -0
  4. package/SECURITY.md +72 -0
  5. package/dist/access/docker-runtime.d.ts +38 -0
  6. package/dist/access/docker-runtime.js +61 -0
  7. package/dist/access/docker-runtime.js.map +1 -0
  8. package/dist/access/index.d.ts +110 -0
  9. package/dist/access/index.js +174 -0
  10. package/dist/access/index.js.map +1 -0
  11. package/dist/access/paradi-auth.d.ts +33 -0
  12. package/dist/access/paradi-auth.js +89 -0
  13. package/dist/access/paradi-auth.js.map +1 -0
  14. package/dist/access/paradi-transport.d.ts +10 -0
  15. package/dist/access/paradi-transport.js +39 -0
  16. package/dist/access/paradi-transport.js.map +1 -0
  17. package/dist/access/paradi-username.d.ts +5 -0
  18. package/dist/access/paradi-username.js +14 -0
  19. package/dist/access/paradi-username.js.map +1 -0
  20. package/dist/access/probe.d.ts +14 -0
  21. package/dist/access/probe.js +63 -0
  22. package/dist/access/probe.js.map +1 -0
  23. package/dist/access/session-pool.d.ts +25 -0
  24. package/dist/access/session-pool.js +46 -0
  25. package/dist/access/session-pool.js.map +1 -0
  26. package/dist/access/ssh-transport.d.ts +16 -0
  27. package/dist/access/ssh-transport.js +74 -0
  28. package/dist/access/ssh-transport.js.map +1 -0
  29. package/dist/access/types.d.ts +43 -0
  30. package/dist/access/types.js +21 -0
  31. package/dist/access/types.js.map +1 -0
  32. package/dist/config.d.ts +52 -0
  33. package/dist/config.js +179 -0
  34. package/dist/config.js.map +1 -0
  35. package/dist/index.d.ts +2 -0
  36. package/dist/index.js +19 -0
  37. package/dist/index.js.map +1 -0
  38. package/dist/instructions.d.ts +4 -0
  39. package/dist/instructions.js +93 -0
  40. package/dist/instructions.js.map +1 -0
  41. package/dist/kubectl/runner.d.ts +32 -0
  42. package/dist/kubectl/runner.js +79 -0
  43. package/dist/kubectl/runner.js.map +1 -0
  44. package/dist/server.d.ts +3 -0
  45. package/dist/server.js +24 -0
  46. package/dist/server.js.map +1 -0
  47. package/dist/ssh/access-client.d.ts +62 -0
  48. package/dist/ssh/access-client.js +470 -0
  49. package/dist/ssh/access-client.js.map +1 -0
  50. package/dist/ssh/access-errors.d.ts +53 -0
  51. package/dist/ssh/access-errors.js +180 -0
  52. package/dist/ssh/access-errors.js.map +1 -0
  53. package/dist/ssh/access-sftp.d.ts +14 -0
  54. package/dist/ssh/access-sftp.js +456 -0
  55. package/dist/ssh/access-sftp.js.map +1 -0
  56. package/dist/ssh/bastion-client.d.ts +60 -0
  57. package/dist/ssh/bastion-client.js +423 -0
  58. package/dist/ssh/bastion-client.js.map +1 -0
  59. package/dist/ssh/bastion-errors.d.ts +62 -0
  60. package/dist/ssh/bastion-errors.js +190 -0
  61. package/dist/ssh/bastion-errors.js.map +1 -0
  62. package/dist/ssh/hop1-sftp.d.ts +14 -0
  63. package/dist/ssh/hop1-sftp.js +437 -0
  64. package/dist/ssh/hop1-sftp.js.map +1 -0
  65. package/dist/tools/copy-job-schemas.d.ts +709 -0
  66. package/dist/tools/copy-job-schemas.js +48 -0
  67. package/dist/tools/copy-job-schemas.js.map +1 -0
  68. package/dist/tools/register.d.ts +14 -0
  69. package/dist/tools/register.js +619 -0
  70. package/dist/tools/register.js.map +1 -0
  71. package/dist/tools/remote-schemas.d.ts +879 -0
  72. package/dist/tools/remote-schemas.js +214 -0
  73. package/dist/tools/remote-schemas.js.map +1 -0
  74. package/dist/utils/copy-executor.d.ts +41 -0
  75. package/dist/utils/copy-executor.js +231 -0
  76. package/dist/utils/copy-executor.js.map +1 -0
  77. package/dist/utils/copy-job-manager.d.ts +34 -0
  78. package/dist/utils/copy-job-manager.js +400 -0
  79. package/dist/utils/copy-job-manager.js.map +1 -0
  80. package/dist/utils/copy-job-store.d.ts +18 -0
  81. package/dist/utils/copy-job-store.js +104 -0
  82. package/dist/utils/copy-job-store.js.map +1 -0
  83. package/dist/utils/copy-job-types.d.ts +133 -0
  84. package/dist/utils/copy-job-types.js +37 -0
  85. package/dist/utils/copy-job-types.js.map +1 -0
  86. package/dist/utils/copy-transfer.d.ts +59 -0
  87. package/dist/utils/copy-transfer.js +86 -0
  88. package/dist/utils/copy-transfer.js.map +1 -0
  89. package/dist/utils/remote-copy.d.ts +14 -0
  90. package/dist/utils/remote-copy.js +115 -0
  91. package/dist/utils/remote-copy.js.map +1 -0
  92. package/dist/utils/remote-executor.d.ts +15 -0
  93. package/dist/utils/remote-executor.js +34 -0
  94. package/dist/utils/remote-executor.js.map +1 -0
  95. package/dist/utils/remote-target.d.ts +22 -0
  96. package/dist/utils/remote-target.js +75 -0
  97. package/dist/utils/remote-target.js.map +1 -0
  98. package/dist/utils/result.d.ts +87 -0
  99. package/dist/utils/result.js +81 -0
  100. package/dist/utils/result.js.map +1 -0
  101. package/dist/utils/shell-quote.d.ts +22 -0
  102. package/dist/utils/shell-quote.js +55 -0
  103. package/dist/utils/shell-quote.js.map +1 -0
  104. package/dist/utils/timeout.d.ts +1 -0
  105. package/dist/utils/timeout.js +18 -0
  106. package/dist/utils/timeout.js.map +1 -0
  107. package/dist/utils/truncate.d.ts +5 -0
  108. package/dist/utils/truncate.js +12 -0
  109. package/dist/utils/truncate.js.map +1 -0
  110. package/dist/version.d.ts +1 -0
  111. package/dist/version.js +7 -0
  112. package/dist/version.js.map +1 -0
  113. package/docs/AGENT_GUIDE.md +161 -0
  114. package/docs/CONFIGURATION.md +130 -0
  115. package/docs/COPY_SPECIALIZED_TEST_CHECKLIST.md +9 -0
  116. package/docs/HOP1_E2E_CHECKLIST.md +128 -0
  117. package/docs/HOP1_E2E_RESULTS.json +647 -0
  118. package/docs/PARADI_ACCESS_PLAN.md +272 -0
  119. package/docs/REMOTE_COPY_JOB_API.md +92 -0
  120. package/docs/TODO.md +66 -0
  121. package/docs/USER_GUIDE.zh-CN.md +93 -0
  122. package/docs/clients/claude-desktop.json.example +12 -0
  123. package/docs/clients/cursor-npx.json.example +12 -0
  124. package/docs/clients/cursor.json.example +12 -0
  125. package/docs/clients/vscode.json.example +12 -0
  126. package/package.json +65 -0
@@ -0,0 +1,46 @@
1
+ import { bashSingleQuote, buildSshRemoteCommand } from "../utils/shell-quote.js";
2
+ /**
3
+ * Session pool keyed by context.key. Context itself never holds clients.
4
+ * Phase 1: access entry ??AccessClient; derived ??parent accessClient + ssh/sshpass.
5
+ */
6
+ export class SessionPool {
7
+ accessClient;
8
+ entries = new Map();
9
+ constructor(accessClient) {
10
+ this.accessClient = accessClient;
11
+ }
12
+ bindAccess(ctx) {
13
+ this.entries.set(ctx.key, { kind: "access" });
14
+ }
15
+ bindDerived(ctx, spec) {
16
+ if (!ctx.parentKey) {
17
+ throw new Error("derived context requires parentKey");
18
+ }
19
+ this.entries.set(ctx.key, {
20
+ kind: "derived",
21
+ parentKey: ctx.parentKey,
22
+ host: spec.host,
23
+ user: spec.user,
24
+ password: spec.password,
25
+ });
26
+ }
27
+ getEntry(contextKey) {
28
+ return this.entries.get(contextKey);
29
+ }
30
+ async exec(ctx, command, timeoutMs, cwd) {
31
+ const entry = this.entries.get(ctx.key);
32
+ if (!entry) {
33
+ throw new Error(`No SessionPool binding for context key=${ctx.key}`);
34
+ }
35
+ if (entry.kind === "access") {
36
+ return this.accessClient.exec(command, timeoutMs, cwd);
37
+ }
38
+ // Derived host: wrap command in ssh from ACCESS (accessClient) session.
39
+ const remoteCommand = cwd
40
+ ? `cd ${bashSingleQuote(cwd)} && ${command}`
41
+ : command;
42
+ const sshCommand = buildSshRemoteCommand(entry.user, entry.host, remoteCommand, entry.password);
43
+ return this.accessClient.exec(sshCommand, timeoutMs);
44
+ }
45
+ }
46
+ //# sourceMappingURL=session-pool.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"session-pool.js","sourceRoot":"","sources":["../../src/access/session-pool.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC;AAajF;;;GAGG;AACH,MAAM,OAAO,WAAW;IAGO;IAFZ,OAAO,GAAG,IAAI,GAAG,EAAqB,CAAC;IAExD,YAA6B,YAA0B;QAA1B,iBAAY,GAAZ,YAAY,CAAc;IAAG,CAAC;IAE3D,UAAU,CAAC,GAAyB;QAClC,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC,CAAC;IAChD,CAAC;IAED,WAAW,CAAC,GAAyB,EAAE,IAAgB;QACrD,IAAI,CAAC,GAAG,CAAC,SAAS,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,EAAE;YACxB,IAAI,EAAE,SAAS;YACf,SAAS,EAAE,GAAG,CAAC,SAAS;YACxB,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,QAAQ,EAAE,IAAI,CAAC,QAAQ;SACxB,CAAC,CAAC;IACL,CAAC;IAED,QAAQ,CAAC,UAAkB;QACzB,OAAO,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IACtC,CAAC;IAED,KAAK,CAAC,IAAI,CACR,GAAyB,EACzB,OAAe,EACf,SAAkB,EAClB,GAAY;QAEZ,MAAM,KAAK,GAAG,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACxC,IAAI,CAAC,KAAK,EAAE,CAAC;YACX,MAAM,IAAI,KAAK,CAAC,0CAA0C,GAAG,CAAC,GAAG,EAAE,CAAC,CAAC;QACvE,CAAC;QACD,IAAI,KAAK,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YAC5B,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,OAAO,EAAE,SAAS,EAAE,GAAG,CAAC,CAAC;QACzD,CAAC;QACD,wEAAwE;QACxE,MAAM,aAAa,GAAG,GAAG;YACvB,CAAC,CAAC,MAAM,eAAe,CAAC,GAAG,CAAC,OAAO,OAAO,EAAE;YAC5C,CAAC,CAAC,OAAO,CAAC;QACZ,MAAM,UAAU,GAAG,qBAAqB,CACtC,KAAK,CAAC,IAAI,EACV,KAAK,CAAC,IAAI,EACV,aAAa,EACb,KAAK,CAAC,QAAQ,CACf,CAAC;QACF,OAAO,IAAI,CAAC,YAAY,CAAC,IAAI,CAAC,UAAU,EAAE,SAAS,CAAC,CAAC;IACvD,CAAC;CACF"}
@@ -0,0 +1,16 @@
1
+ import type { AppConfig } from "../config.js";
2
+ import { type DeriveSpec, type HostExecutionContext } from "./types.js";
3
+ /**
4
+ * SSH AccessTransport: produce HostExecutionContext only (no sessions).
5
+ * Phase 1: ACCESS_KIND=ssh. Paradi is Phase 3.
6
+ */
7
+ export declare class SshAccessTransport {
8
+ createAccessContext(config: AppConfig): HostExecutionContext;
9
+ /**
10
+ * Derive a child context for a second-hop host.
11
+ * Does not open SSH; SessionPool binds credentials separately.
12
+ */
13
+ deriveContext(parent: HostExecutionContext, spec: DeriveSpec): HostExecutionContext;
14
+ /** Default derive target from config (selector only ??does not create a Context). */
15
+ defaultDeriveSpec(config: AppConfig): DeriveSpec | undefined;
16
+ }
@@ -0,0 +1,74 @@
1
+ import { createHash } from "node:crypto";
2
+ import { buildContextKey, initialCapabilities, } from "./types.js";
3
+ function deriveAuthIdentity(spec) {
4
+ if (!spec.password) {
5
+ return "key-or-agent";
6
+ }
7
+ const digest = createHash("sha256").update(spec.password).digest("hex").slice(0, 16);
8
+ return `pw:${digest}`;
9
+ }
10
+ /**
11
+ * SSH AccessTransport: produce HostExecutionContext only (no sessions).
12
+ * Phase 1: ACCESS_KIND=ssh. Paradi is Phase 3.
13
+ */
14
+ export class SshAccessTransport {
15
+ createAccessContext(config) {
16
+ if (config.accessKind !== "ssh") {
17
+ throw new Error(`SshAccessTransport only supports ACCESS_KIND=ssh (got ${config.accessKind})`);
18
+ }
19
+ const host = config.accessHost;
20
+ const user = config.accessUser;
21
+ return {
22
+ key: buildContextKey({
23
+ transport: "ssh",
24
+ host,
25
+ user,
26
+ port: config.accessPort,
27
+ authIdentity: user,
28
+ }),
29
+ transport: "ssh",
30
+ endpoint: { host, user, port: config.accessPort },
31
+ capabilities: initialCapabilities(true),
32
+ };
33
+ }
34
+ /**
35
+ * Derive a child context for a second-hop host.
36
+ * Does not open SSH; SessionPool binds credentials separately.
37
+ */
38
+ deriveContext(parent, spec) {
39
+ if (parent.transport !== "ssh") {
40
+ throw new Error(`deriveContext requires parent transport=ssh (got ${parent.transport})`);
41
+ }
42
+ return {
43
+ key: buildContextKey({
44
+ transport: "ssh",
45
+ host: spec.host,
46
+ user: spec.user,
47
+ port: spec.port ?? 22,
48
+ parentKey: parent.key,
49
+ authIdentity: deriveAuthIdentity(spec),
50
+ }),
51
+ transport: "ssh",
52
+ endpoint: { host: spec.host, user: spec.user, port: spec.port ?? 22 },
53
+ parentKey: parent.key,
54
+ capabilities: initialCapabilities(true),
55
+ };
56
+ }
57
+ /** Default derive target from config (selector only ??does not create a Context). */
58
+ defaultDeriveSpec(config) {
59
+ const host = config.targetHost?.trim();
60
+ if (!host) {
61
+ return undefined;
62
+ }
63
+ const user = config.targetUser?.trim();
64
+ if (!user) {
65
+ throw new Error("TARGET_HOST is set but TARGET_USER is missing");
66
+ }
67
+ return {
68
+ host,
69
+ user,
70
+ password: config.targetPassword,
71
+ };
72
+ }
73
+ }
74
+ //# sourceMappingURL=ssh-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ssh-transport.js","sourceRoot":"","sources":["../../src/access/ssh-transport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC,OAAO,EACL,eAAe,EACf,mBAAmB,GAGpB,MAAM,YAAY,CAAC;AAEpB,SAAS,kBAAkB,CAAC,IAAgB;IAC1C,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;QACnB,OAAO,cAAc,CAAC;IACxB,CAAC;IACD,MAAM,MAAM,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrF,OAAO,MAAM,MAAM,EAAE,CAAC;AACxB,CAAC;AAED;;;GAGG;AACH,MAAM,OAAO,kBAAkB;IAC7B,mBAAmB,CAAC,MAAiB;QACnC,IAAI,MAAM,CAAC,UAAU,KAAK,KAAK,EAAE,CAAC;YAChC,MAAM,IAAI,KAAK,CACb,yDAAyD,MAAM,CAAC,UAAU,GAAG,CAC9E,CAAC;QACJ,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;QAC/B,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,CAAC;QAC/B,OAAO;YACL,GAAG,EAAE,eAAe,CAAC;gBACnB,SAAS,EAAE,KAAK;gBAChB,IAAI;gBACJ,IAAI;gBACJ,IAAI,EAAE,MAAM,CAAC,UAAU;gBACvB,YAAY,EAAE,IAAI;aACnB,CAAC;YACF,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,CAAC,UAAU,EAAE;YACjD,YAAY,EAAE,mBAAmB,CAAC,IAAI,CAAC;SACxC,CAAC;IACJ,CAAC;IAED;;;OAGG;IACH,aAAa,CAAC,MAA4B,EAAE,IAAgB;QAC1D,IAAI,MAAM,CAAC,SAAS,KAAK,KAAK,EAAE,CAAC;YAC/B,MAAM,IAAI,KAAK,CAAC,oDAAoD,MAAM,CAAC,SAAS,GAAG,CAAC,CAAC;QAC3F,CAAC;QACD,OAAO;YACL,GAAG,EAAE,eAAe,CAAC;gBACnB,SAAS,EAAE,KAAK;gBAChB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE;gBACrB,SAAS,EAAE,MAAM,CAAC,GAAG;gBACrB,YAAY,EAAE,kBAAkB,CAAC,IAAI,CAAC;aACvC,CAAC;YACF,SAAS,EAAE,KAAK;YAChB,QAAQ,EAAE,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,EAAE,EAAE;YACrE,SAAS,EAAE,MAAM,CAAC,GAAG;YACrB,YAAY,EAAE,mBAAmB,CAAC,IAAI,CAAC;SACxC,CAAC;IACJ,CAAC;IAED,qFAAqF;IACrF,iBAAiB,CAAC,MAAiB;QACjC,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,OAAO,SAAS,CAAC;QACnB,CAAC;QACD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;QACvC,IAAI,CAAC,IAAI,EAAE,CAAC;YACV,MAAM,IAAI,KAAK,CAAC,+CAA+C,CAAC,CAAC;QACnE,CAAC;QACD,OAAO;YACL,IAAI;YACJ,IAAI;YACJ,QAAQ,EAAE,MAAM,CAAC,cAAc;SAChC,CAAC;IACJ,CAAC;CACF"}
@@ -0,0 +1,43 @@
1
+ export type AccessTransportKind = "ssh" | "paradi-gateway";
2
+ /** Capability tri-state: never infer docker/kubectl from SSH success alone. */
3
+ export type Cap = true | false | "unknown";
4
+ export interface ContextCapabilities {
5
+ exec: Cap;
6
+ docker: Cap;
7
+ kubectl: Cap;
8
+ /** Declared by transport (e.g. paradi); not overwritten by CLI probe. */
9
+ sftp?: Cap | "degraded";
10
+ /** Gateway may rate-limit SSH channel opens ? avoid parallel hammering. */
11
+ channelOpenSensitive?: boolean;
12
+ }
13
+ /**
14
+ * Host execution descriptor ? must NOT hold SSH sessions or live clients.
15
+ * Connections live in SessionPool, keyed by stable `key`.
16
+ */
17
+ export interface HostExecutionContext {
18
+ key: string;
19
+ transport: AccessTransportKind;
20
+ endpoint: {
21
+ host: string;
22
+ user?: string;
23
+ port?: number;
24
+ };
25
+ parentKey?: string;
26
+ capabilities: ContextCapabilities;
27
+ }
28
+ export interface DeriveSpec {
29
+ host: string;
30
+ user: string;
31
+ password?: string;
32
+ port?: number;
33
+ }
34
+ export declare function initialCapabilities(exec?: Cap): ContextCapabilities;
35
+ /** Stable pool key ? never a per-call random uuid. */
36
+ export declare function buildContextKey(parts: {
37
+ transport: AccessTransportKind;
38
+ host: string;
39
+ user?: string;
40
+ port?: number;
41
+ parentKey?: string;
42
+ authIdentity?: string;
43
+ }): string;
@@ -0,0 +1,21 @@
1
+ import { createHash } from "node:crypto";
2
+ export function initialCapabilities(exec = true) {
3
+ return {
4
+ exec,
5
+ docker: "unknown",
6
+ kubectl: "unknown",
7
+ };
8
+ }
9
+ /** Stable pool key ? never a per-call random uuid. */
10
+ export function buildContextKey(parts) {
11
+ const payload = [
12
+ parts.transport,
13
+ parts.host,
14
+ parts.user ?? "",
15
+ String(parts.port ?? 22),
16
+ parts.parentKey ?? "",
17
+ parts.authIdentity ?? "",
18
+ ].join("|");
19
+ return createHash("sha256").update(payload).digest("hex").slice(0, 24);
20
+ }
21
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/access/types.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAoCzC,MAAM,UAAU,mBAAmB,CAAC,OAAY,IAAI;IAClD,OAAO;QACL,IAAI;QACJ,MAAM,EAAE,SAAS;QACjB,OAAO,EAAE,SAAS;KACnB,CAAC;AACJ,CAAC;AAED,sDAAsD;AACtD,MAAM,UAAU,eAAe,CAAC,KAO/B;IACC,MAAM,OAAO,GAAG;QACd,KAAK,CAAC,SAAS;QACf,KAAK,CAAC,IAAI;QACV,KAAK,CAAC,IAAI,IAAI,EAAE;QAChB,MAAM,CAAC,KAAK,CAAC,IAAI,IAAI,EAAE,CAAC;QACxB,KAAK,CAAC,SAAS,IAAI,EAAE;QACrB,KAAK,CAAC,YAAY,IAAI,EAAE;KACzB,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACZ,OAAO,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;AACzE,CAAC"}
@@ -0,0 +1,52 @@
1
+ import type { AccessTransportKind } from "./access/types.js";
2
+ import { type ParadiAuthSetting } from "./access/paradi-auth.js";
3
+ export interface AppConfig {
4
+ accessKind: AccessTransportKind;
5
+ accessHost: string;
6
+ accessUser: string;
7
+ accessPort: number;
8
+ accessPrivateKeyPath?: string;
9
+ accessPassword?: string;
10
+ /**
11
+ * Paradi gateway auth encoding. Default auto = try slash then compound; pin winner
12
+ * for this MCP process. Customers normally leave unset.
13
+ */
14
+ accessParadiAuth?: ParadiAuthSetting;
15
+ /** Default derive selector (ssh only) — does not create a Context at init. */
16
+ targetHost?: string;
17
+ targetUser?: string;
18
+ targetPassword?: string;
19
+ defaultTimeoutMs: number;
20
+ maxOutputChars: number;
21
+ /** Policy (k8s only); not part of Access matrix. Prefer ACCESS_POLICY_ALLOWED_NAMESPACES. */
22
+ allowedNamespaces?: string[];
23
+ }
24
+ /**
25
+ * Hop-1 SSH identity: where we TCP-connect and which username to present.
26
+ * Paradi: uses pinned/auto-preferred mode for username (password resolved separately).
27
+ */
28
+ export declare function resolveAccessSshIdentity(config: AppConfig): {
29
+ host: string;
30
+ port: number;
31
+ username: string;
32
+ };
33
+ /**
34
+ * Load MCP config. Only ACCESS_* / TARGET_* (and policy ALLOWED_NAMESPACES).
35
+ * Legacy BASTION_* / REMOTE_SSH_* aliases were removed in 4.0.0.
36
+ */
37
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): AppConfig;
38
+ export declare function assertNamespaceAllowed(config: AppConfig, namespace: string): void;
39
+ export declare function resolveDefaultKeyPaths(homeDir?: string, exists?: (path: string) => boolean): string[];
40
+ export interface ResolveSshAuthDeps {
41
+ env?: NodeJS.ProcessEnv;
42
+ homeDir?: string;
43
+ existsSync?: (path: string) => boolean;
44
+ readFileSync?: (path: string) => Buffer;
45
+ }
46
+ /** Collect every configured ACCESS auth method; ssh2 tries them until one succeeds. */
47
+ export declare function resolveSshAuthOptions(config: AppConfig, deps?: ResolveSshAuthDeps): {
48
+ privateKey?: Buffer | string;
49
+ password?: string;
50
+ agent?: string;
51
+ authMethods: string[];
52
+ };
package/dist/config.js ADDED
@@ -0,0 +1,179 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+ import { getPinnedParadiAuth, parseParadiAuthSetting, resolveParadiConnectParams, } from "./access/paradi-auth.js";
5
+ function parseAllowedNamespaces(value) {
6
+ if (!value?.trim()) {
7
+ return undefined;
8
+ }
9
+ const list = value
10
+ .split(",")
11
+ .map((item) => item.trim())
12
+ .filter(Boolean);
13
+ return list.length > 0 ? list : undefined;
14
+ }
15
+ /**
16
+ * Hop-1 SSH identity: where we TCP-connect and which username to present.
17
+ * Paradi: uses pinned/auto-preferred mode for username (password resolved separately).
18
+ */
19
+ export function resolveAccessSshIdentity(config) {
20
+ if (config.accessKind === "paradi-gateway") {
21
+ const mode = getPinnedParadiAuth(config) ??
22
+ (config.accessParadiAuth === "compound" ? "compound" : "slash");
23
+ const params = resolveParadiConnectParams(config, mode);
24
+ return {
25
+ host: params.host,
26
+ port: params.port,
27
+ username: params.username,
28
+ };
29
+ }
30
+ return {
31
+ host: config.accessHost,
32
+ port: config.accessPort,
33
+ username: config.accessUser,
34
+ };
35
+ }
36
+ function firstDefined(...values) {
37
+ for (const value of values) {
38
+ const trimmed = value?.trim();
39
+ if (trimmed) {
40
+ return trimmed;
41
+ }
42
+ }
43
+ return undefined;
44
+ }
45
+ /**
46
+ * Load MCP config. Only ACCESS_* / TARGET_* (and policy ALLOWED_NAMESPACES).
47
+ * Legacy BASTION_* / REMOTE_SSH_* aliases were removed in 4.0.0.
48
+ */
49
+ export function loadConfig(env = process.env) {
50
+ rejectRemovedEnvAliases(env);
51
+ const kindRaw = (env.ACCESS_KIND?.trim() || "ssh").toLowerCase();
52
+ if (kindRaw !== "ssh" && kindRaw !== "paradi-gateway") {
53
+ throw new Error(`ACCESS_KIND must be "ssh" or "paradi-gateway" (got "${env.ACCESS_KIND}")`);
54
+ }
55
+ const accessKind = kindRaw;
56
+ const accessHost = env.ACCESS_HOST?.trim();
57
+ const accessUser = env.ACCESS_USER?.trim();
58
+ if (!accessHost) {
59
+ throw new Error("ACCESS_HOST is required");
60
+ }
61
+ if (!accessUser) {
62
+ throw new Error("ACCESS_USER is required");
63
+ }
64
+ const port = Number(env.ACCESS_PORT?.trim() || "22");
65
+ if (!Number.isFinite(port) || port <= 0) {
66
+ throw new Error("ACCESS_PORT must be a positive number");
67
+ }
68
+ const targetHost = env.TARGET_HOST?.trim() || undefined;
69
+ const targetUser = env.TARGET_USER?.trim() || undefined;
70
+ // TARGET_PASSWORD ignored for paradi default auth (gateway password is ACCESS_PASSWORD)
71
+ const targetPassword = accessKind === "paradi-gateway"
72
+ ? undefined
73
+ : env.TARGET_PASSWORD || undefined;
74
+ if (accessKind === "paradi-gateway") {
75
+ if (!targetHost || !targetUser) {
76
+ throw new Error("ACCESS_KIND=paradi-gateway requires TARGET_HOST and TARGET_USER");
77
+ }
78
+ }
79
+ else if (targetHost && !targetUser) {
80
+ throw new Error("TARGET_HOST is set but TARGET_USER is missing (do not fallback to ACCESS_USER)");
81
+ }
82
+ const accessParadiAuth = accessKind === "paradi-gateway"
83
+ ? parseParadiAuthSetting(env.ACCESS_PARADI_AUTH)
84
+ : undefined;
85
+ const defaultTimeoutMs = Number(env.DEFAULT_TIMEOUT_MS ?? "60000");
86
+ const maxOutputChars = Number(env.MAX_OUTPUT_CHARS ?? "65536");
87
+ if (!Number.isFinite(defaultTimeoutMs) || defaultTimeoutMs <= 0) {
88
+ throw new Error("DEFAULT_TIMEOUT_MS must be a positive number");
89
+ }
90
+ if (!Number.isFinite(maxOutputChars) || maxOutputChars <= 0) {
91
+ throw new Error("MAX_OUTPUT_CHARS must be a positive number");
92
+ }
93
+ return {
94
+ accessKind,
95
+ accessHost,
96
+ accessUser,
97
+ accessPort: port,
98
+ accessPrivateKeyPath: env.ACCESS_PRIVATE_KEY_PATH?.trim() || undefined,
99
+ accessPassword: env.ACCESS_PASSWORD || undefined,
100
+ accessParadiAuth,
101
+ targetHost,
102
+ targetUser,
103
+ targetPassword,
104
+ defaultTimeoutMs,
105
+ maxOutputChars,
106
+ allowedNamespaces: parseAllowedNamespaces(firstDefined(env.ACCESS_POLICY_ALLOWED_NAMESPACES, env.ALLOWED_NAMESPACES)),
107
+ };
108
+ }
109
+ /** Fail fast with rename hints when removed env names are still set. */
110
+ function rejectRemovedEnvAliases(env) {
111
+ const removed = [
112
+ ["BASTION_HOST", "ACCESS_HOST"],
113
+ ["BASTION_USER", "ACCESS_USER"],
114
+ ["BASTION_PORT", "ACCESS_PORT"],
115
+ ["BASTION_PASSWORD", "ACCESS_PASSWORD"],
116
+ ["BASTION_PRIVATE_KEY_PATH", "ACCESS_PRIVATE_KEY_PATH"],
117
+ ["REMOTE_SSH_USER", "TARGET_USER"],
118
+ ["REMOTE_SSH_PASSWORD", "TARGET_PASSWORD"],
119
+ ];
120
+ const hits = removed.filter(([oldName]) => Boolean(env[oldName]?.trim()));
121
+ if (hits.length === 0) {
122
+ return;
123
+ }
124
+ const hints = hits.map(([oldName, neu]) => `${oldName} ? ${neu}`).join("; ");
125
+ throw new Error(`Removed env aliases (4.0.0): ${hints}. Use ACCESS_* / TARGET_* only.`);
126
+ }
127
+ export function assertNamespaceAllowed(config, namespace) {
128
+ if (!config.allowedNamespaces) {
129
+ return;
130
+ }
131
+ if (!config.allowedNamespaces.includes(namespace)) {
132
+ throw new Error(`Namespace "${namespace}" is not allowed. Allowed: ${config.allowedNamespaces.join(", ")}`);
133
+ }
134
+ }
135
+ export function resolveDefaultKeyPaths(homeDir = os.homedir(), exists = fs.existsSync) {
136
+ const sshDir = path.join(homeDir, ".ssh");
137
+ return ["id_ed25519", "id_rsa", "id_ecdsa"]
138
+ .map((name) => path.join(sshDir, name))
139
+ .filter((keyPath) => exists(keyPath));
140
+ }
141
+ /** Collect every configured ACCESS auth method; ssh2 tries them until one succeeds. */
142
+ export function resolveSshAuthOptions(config, deps = {}) {
143
+ const env = deps.env ?? process.env;
144
+ const existsSync = deps.existsSync ?? fs.existsSync;
145
+ const readFileSync = deps.readFileSync ?? fs.readFileSync;
146
+ const homeDir = deps.homeDir ?? os.homedir();
147
+ const authMethods = [];
148
+ let privateKey;
149
+ if (config.accessPrivateKeyPath) {
150
+ if (!existsSync(config.accessPrivateKeyPath)) {
151
+ throw new Error(`ACCESS_PRIVATE_KEY_PATH not found: ${config.accessPrivateKeyPath}`);
152
+ }
153
+ privateKey = readFileSync(config.accessPrivateKeyPath);
154
+ authMethods.push(`key:${config.accessPrivateKeyPath}`);
155
+ }
156
+ else {
157
+ for (const keyPath of resolveDefaultKeyPaths(homeDir, existsSync)) {
158
+ privateKey = readFileSync(keyPath);
159
+ authMethods.push(`key:${keyPath}`);
160
+ break;
161
+ }
162
+ }
163
+ const agentSocket = env.SSH_AUTH_SOCK?.trim();
164
+ let agent;
165
+ if (agentSocket && existsSync(agentSocket)) {
166
+ agent = agentSocket;
167
+ authMethods.push("agent");
168
+ }
169
+ let password;
170
+ if (config.accessPassword) {
171
+ password = config.accessPassword;
172
+ authMethods.push("password");
173
+ }
174
+ if (!privateKey && !agent && !password) {
175
+ throw new Error("No SSH authentication available. Configure SSH agent, ACCESS_PRIVATE_KEY_PATH, default ~/.ssh keys, or ACCESS_PASSWORD.");
176
+ }
177
+ return { privateKey, password, agent, authMethods };
178
+ }
179
+ //# sourceMappingURL=config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
package/dist/index.js ADDED
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
3
+ import { loadConfig } from "./config.js";
4
+ import { createServer } from "./server.js";
5
+ async function main() {
6
+ const config = loadConfig();
7
+ const server = createServer(config);
8
+ const transport = new StdioServerTransport();
9
+ process.on("SIGINT", () => {
10
+ process.exit(0);
11
+ });
12
+ await server.connect(transport);
13
+ }
14
+ main().catch((error) => {
15
+ const message = error instanceof Error ? error.message : String(error);
16
+ console.error(`[host-execution-mcp] Fatal: ${message}`);
17
+ process.exit(1);
18
+ });
19
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,2CAA2C,CAAC;AACjF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAE3C,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GAAG,UAAU,EAAE,CAAC;IAC5B,MAAM,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC,CAAC;IACpC,MAAM,SAAS,GAAG,IAAI,oBAAoB,EAAE,CAAC;IAE7C,OAAO,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;IAEH,MAAM,MAAM,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;AAClC,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,KAAc,EAAE,EAAE;IAC9B,MAAM,OAAO,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;IACvE,OAAO,CAAC,KAAK,CAAC,+BAA+B,OAAO,EAAE,CAAC,CAAC;IACxD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AAClB,CAAC,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ import type { AppConfig } from "./config.js";
2
+ /** TARGET_* default derive auth so agents omit configured defaults instead of asking the human. */
3
+ export declare function buildDerivedHostAuthGuidance(config: AppConfig): string;
4
+ export declare function buildServerInstructions(config: AppConfig): string;
@@ -0,0 +1,93 @@
1
+ function buildSshBaseInstructions() {
2
+ return `You are connected to host-execution-mcp, a HostExecutionContext-based remote execution MCP.
3
+
4
+ Execution model:
5
+ Access: MCP client → AccessTransport → HostExecutionContext (landing)
6
+ Runtime: selected Context → shell / docker / k8s / copy (SessionPool; Runtime never creates SSH)
7
+ Derive: context_derive → contextKey (ACCESS_KIND=ssh only). NOT SSH from the laptop.
8
+
9
+ Context vs shell:
10
+ - context_derive — create a derived HostExecutionContext; returns contextKey (does not probe reachability)
11
+ - shell_exec — shell on a Context; never creates Context (omit contextKey = AccessTransport landing)
12
+ - ssh_exec — shortcut for context_derive + shell_exec; returns reusable contextKey. Prefer for derive+shell in one call; reuse the key for follow-up docker_*/remote_*/shell_exec
13
+
14
+ docker_* / context_probe: Runtime on selected Context.
15
+ Omit contextKey = AccessTransport landing (ACCESS under ssh; TARGET under paradi-gateway).
16
+
17
+ remote_* target=context = HostExecutionContext by contextKey (from context_derive / ssh_exec).
18
+
19
+ Tool selection:
20
+ - access_check — Local→ACCESS connectivity (≤10s; NOT kubectl). First session / after ACCESS_UNAVAILABLE
21
+ - shell_exec — landing or derived shell (contextKey)
22
+ - context_derive — need a contextKey without running a command yet
23
+ - ssh_exec — one-shot derive+shell (returns contextKey)
24
+ - docker_* — Docker on landing or contextKey
25
+ - k8s_* — kubectl on landing only (by design). kubectl on a derived host: shell_exec(contextKey, "kubectl …")
26
+ - remote_* target=landing|pod|context — files/ps/copy
27
+ - remote_copy_file vs remote_copy_start — sync vs async; never auto-promote; prefer start when size>256MB or ETA>2min
28
+
29
+ Hard rules:
30
+ - access_check.reachable=false or code=ACCESS_UNAVAILABLE → stop (VPN / ACCESS_HOST); do not retry other tools
31
+ - SSH success ≠ docker/kubectl; unknown ≠ true — never invent true; never reuse capabilities across Contexts
32
+ - Before pkill/kill/rm: remote_ps or user confirmation
33
+ - Never assume namespace/paths; never reuse ACCESS_USER as TARGET_USER / derive user
34
+ - success=false: read stdout (stderr merged). truncated → more lines / remote_copy_*
35
+ - Multi-container pods: pass container name
36
+ - Operational cookbook (shell quoting, CRLF, docker login quirks, registry probes): see docs/AGENT_GUIDE.md`;
37
+ }
38
+ function buildParadiBaseInstructions(config) {
39
+ const target = config.targetHost?.trim() || "<TARGET_HOST>";
40
+ return `You are connected to host-execution-mcp, a HostExecutionContext-based remote execution MCP (ACCESS_KIND=paradi-gateway).
41
+
42
+ Landing endpoint is TARGET (${target}) — not the gateway OS. Runtime does not know gateway details; never creates SSH.
43
+
44
+ Derive / context_derive / ssh_exec / remote_* target=context / contextKey: unsupported by current Paradi AccessTransport (landing is already TARGET). Handlers throw if called — omit contextKey; use target=landing|pod|local.
45
+
46
+ Default workflow:
47
+ - Shell: shell_exec or remote_* target=landing
48
+ - Docker / context_probe: omit contextKey (landing = TARGET)
49
+ - Pods: k8s_* / remote_* target=pod (kubectl on landing)
50
+ - Copy: local↔landing or pod/local; SFTP degraded — prefer remote_copy_start for large files
51
+
52
+ Hard rules:
53
+ - access_check.reachable=false or ACCESS_UNAVAILABLE → stop (VPN / ACCESS_HOST)
54
+ - unknown ≠ true; SSH success ≠ docker/kubectl; never reuse capabilities across Contexts
55
+ - Before pkill/kill/rm: remote_ps or user confirmation
56
+ - Never assume namespace/paths
57
+ - Operational cookbook: docs/AGENT_GUIDE.md`;
58
+ }
59
+ /** TARGET_* default derive auth so agents omit configured defaults instead of asking the human. */
60
+ export function buildDerivedHostAuthGuidance(config) {
61
+ if (config.accessKind === "paradi-gateway") {
62
+ const host = config.targetHost?.trim();
63
+ return `
64
+ Paradi landing: Context endpoint is TARGET${host ? ` (${host})` : ""}. Derive unsupported by current Paradi AccessTransport — omit contextKey; context_derive / ssh_exec / target=context throw if called. Use shell_exec, docker_*, k8s_*, target=landing|pod|local.`;
65
+ }
66
+ const user = config.targetUser?.trim();
67
+ const passwordSet = Boolean(config.targetPassword);
68
+ const host = config.targetHost?.trim();
69
+ const hostHint = host ? ` TARGET_HOST=${host}.` : "";
70
+ const noAccessUserFallback = " Never reuse ACCESS_USER as TARGET_USER / derive user — pass TARGET_USER (or tool user) explicitly.";
71
+ const shortcutHint = " ssh_exec = context_derive + shell_exec shortcut (returns reusable contextKey).";
72
+ if (!user && !passwordSet) {
73
+ return `
74
+ Derive host auth: TARGET_* unset — pass user (password if no key).${hostHint}${noAccessUserFallback}${shortcutHint} Do not invent defaults.`;
75
+ }
76
+ if (user && passwordSet) {
77
+ return `
78
+ Derive host auth: TARGET_* active (user=${user}, password=set)${hostHint} — omit user/password on context_derive / ssh_exec; do not ask the human. Override only for a different account.${noAccessUserFallback}${shortcutHint}`;
79
+ }
80
+ if (user) {
81
+ return `
82
+ Derive host auth: TARGET_USER=${user}${hostHint} — omit user on context_derive / ssh_exec; pass password only if no key.${noAccessUserFallback}${shortcutHint}`;
83
+ }
84
+ return `
85
+ Derive host auth: TARGET_PASSWORD set${hostHint} — omit password on context_derive / ssh_exec; still pass user (TARGET_USER unset). Do not invent a username.${noAccessUserFallback}${shortcutHint}`;
86
+ }
87
+ export function buildServerInstructions(config) {
88
+ const base = config.accessKind === "paradi-gateway"
89
+ ? buildParadiBaseInstructions(config)
90
+ : buildSshBaseInstructions();
91
+ return `${base}${buildDerivedHostAuthGuidance(config)}`;
92
+ }
93
+ //# sourceMappingURL=instructions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instructions.js","sourceRoot":"","sources":["../src/instructions.ts"],"names":[],"mappings":"AAEA,SAAS,wBAAwB;IAC/B,OAAO;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;4GAkCmG,CAAC;AAC7G,CAAC;AAED,SAAS,2BAA2B,CAAC,MAAiB;IACpD,MAAM,MAAM,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,IAAI,eAAe,CAAC;IAC5D,OAAO;;8BAEqB,MAAM;;;;;;;;;;;;;;;4CAeQ,CAAC;AAC7C,CAAC;AAED,mGAAmG;AACnG,MAAM,UAAU,4BAA4B,CAAC,MAAiB;IAC5D,IAAI,MAAM,CAAC,UAAU,KAAK,gBAAgB,EAAE,CAAC;QAC3C,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;QACvC,OAAO;4CACiC,IAAI,CAAC,CAAC,CAAC,KAAK,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,kMAAkM,CAAC;IACrQ,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;IACvC,MAAM,WAAW,GAAG,OAAO,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC;IACnD,MAAM,IAAI,GAAG,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,CAAC;IACvC,MAAM,QAAQ,GAAG,IAAI,CAAC,CAAC,CAAC,gBAAgB,IAAI,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC;IACrD,MAAM,oBAAoB,GACxB,qGAAqG,CAAC;IACxG,MAAM,YAAY,GAChB,iFAAiF,CAAC;IAEpF,IAAI,CAAC,IAAI,IAAI,CAAC,WAAW,EAAE,CAAC;QAC1B,OAAO;oEACyD,QAAQ,GAAG,oBAAoB,GAAG,YAAY,0BAA0B,CAAC;IAC3I,CAAC;IAED,IAAI,IAAI,IAAI,WAAW,EAAE,CAAC;QACxB,OAAO;0CAC+B,IAAI,kBAAkB,QAAQ,mHAAmH,oBAAoB,GAAG,YAAY,EAAE,CAAC;IAC/N,CAAC;IAED,IAAI,IAAI,EAAE,CAAC;QACT,OAAO;gCACqB,IAAI,GAAG,QAAQ,2EAA2E,oBAAoB,GAAG,YAAY,EAAE,CAAC;IAC9J,CAAC;IAED,OAAO;uCAC8B,QAAQ,gHAAgH,oBAAoB,GAAG,YAAY,EAAE,CAAC;AACrM,CAAC;AAED,MAAM,UAAU,uBAAuB,CAAC,MAAiB;IACvD,MAAM,IAAI,GACR,MAAM,CAAC,UAAU,KAAK,gBAAgB;QACpC,CAAC,CAAC,2BAA2B,CAAC,MAAM,CAAC;QACrC,CAAC,CAAC,wBAAwB,EAAE,CAAC;IACjC,OAAO,GAAG,IAAI,GAAG,4BAA4B,CAAC,MAAM,CAAC,EAAE,CAAC;AAC1D,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { AppConfig } from "../config.js";
2
+ import type { AccessClient } from "../ssh/access-client.js";
3
+ export type PodOutputFormat = "wide" | "json" | "name";
4
+ export declare function buildListPodsCommand(namespace: string, options: {
5
+ labelSelector?: string;
6
+ fieldSelector?: string;
7
+ output?: PodOutputFormat;
8
+ }): string;
9
+ export declare function buildKubectlExecCommand(namespace: string, pod: string, command: string, container?: string): string;
10
+ export declare function buildKubectlLogsCommand(namespace: string, pod: string, options: {
11
+ container?: string;
12
+ tail?: number;
13
+ since?: string;
14
+ }): string;
15
+ export declare function runOnAccess(client: AccessClient, config: AppConfig, command: string, timeoutMs?: number, cwd?: string): Promise<import("../ssh/access-client.js").ExecOutput>;
16
+ export declare function validateNamespace(config: AppConfig, namespace: string): void;
17
+ export declare class KubectlRunner {
18
+ private readonly client;
19
+ private readonly config;
20
+ constructor(client: AccessClient, config: AppConfig);
21
+ listPods(namespace: string, options: {
22
+ labelSelector?: string;
23
+ fieldSelector?: string;
24
+ output?: PodOutputFormat;
25
+ }, timeoutMs?: number): Promise<import("../ssh/access-client.js").ExecOutput>;
26
+ exec(namespace: string, pod: string, command: string, container?: string, timeoutMs?: number): Promise<import("../ssh/access-client.js").ExecOutput>;
27
+ logs(namespace: string, pod: string, options: {
28
+ container?: string;
29
+ tail?: number;
30
+ since?: string;
31
+ }, timeoutMs?: number): Promise<import("../ssh/access-client.js").ExecOutput>;
32
+ }