@grackle-ai/adapter-sdk 0.36.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.
@@ -0,0 +1,51 @@
1
+ import type { Client } from "@connectrpc/connect";
2
+ import type { powerline } from "@grackle-ai/common";
3
+ import type { AdapterLogger } from "./logger.js";
4
+ /** Type-safe ConnectRPC client for the PowerLine gRPC service. */
5
+ export type PowerLineClient = Client<typeof powerline.GracklePowerLine>;
6
+ /** An active connection to a PowerLine, including the gRPC client and port info. */
7
+ export interface PowerLineConnection {
8
+ client: PowerLineClient;
9
+ environmentId: string;
10
+ port: number;
11
+ }
12
+ /** Progress event emitted during environment provisioning. */
13
+ export interface ProvisionEvent {
14
+ stage: string;
15
+ message: string;
16
+ progress: number;
17
+ }
18
+ /** Base configuration shared by all environment adapters. */
19
+ export interface BaseEnvironmentConfig {
20
+ /** Override the default PowerLine port. */
21
+ port?: number;
22
+ /** Override the host to connect to. */
23
+ host?: string;
24
+ }
25
+ /** Contract that all environment adapter backends must implement. */
26
+ export interface EnvironmentAdapter {
27
+ type: string;
28
+ /** Provision infrastructure and yield progress events. */
29
+ provision(environmentId: string, config: Record<string, unknown>, powerlineToken: string): AsyncGenerator<ProvisionEvent>;
30
+ /** Establish a gRPC connection to the PowerLine running in the environment. */
31
+ connect(environmentId: string, config: Record<string, unknown>, powerlineToken: string): Promise<PowerLineConnection>;
32
+ /** Release resources associated with a connection without stopping the environment. */
33
+ disconnect(environmentId: string): Promise<void>;
34
+ /** Stop the environment's underlying compute (e.g. stop a Docker container). */
35
+ stop(environmentId: string, config: Record<string, unknown>): Promise<void>;
36
+ /** Permanently destroy the environment's underlying compute. */
37
+ destroy(environmentId: string, config: Record<string, unknown>): Promise<void>;
38
+ /** Return true if the PowerLine is reachable via ping. */
39
+ healthCheck(connection: PowerLineConnection): Promise<boolean>;
40
+ /** Attempt fast reconnect without re-bootstrapping. Throws if PowerLine cannot be restarted. */
41
+ reconnect?(environmentId: string, config: Record<string, unknown>, powerlineToken: string): AsyncGenerator<ProvisionEvent>;
42
+ }
43
+ /**
44
+ * Try fast reconnect if the adapter supports it and the environment was
45
+ * previously bootstrapped, falling back to full provision on any error.
46
+ *
47
+ * Yields {@link ProvisionEvent}s from whichever path runs, allowing callers
48
+ * (gRPC streaming and WebSocket broadcast) to forward them uniformly.
49
+ */
50
+ export declare function reconnectOrProvision(environmentId: string, adapter: EnvironmentAdapter, config: Record<string, unknown>, powerlineToken: string, bootstrapped: boolean, logger?: AdapterLogger): AsyncGenerator<ProvisionEvent>;
51
+ //# sourceMappingURL=adapter.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,kEAAkE;AAClE,MAAM,MAAM,eAAe,GAAG,MAAM,CAAC,OAAO,SAAS,CAAC,gBAAgB,CAAC,CAAC;AAExE,oFAAoF;AACpF,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,eAAe,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8DAA8D;AAC9D,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,6DAA6D;AAC7D,MAAM,WAAW,qBAAqB;IACpC,2CAA2C;IAC3C,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,qEAAqE;AACrE,MAAM,WAAW,kBAAkB;IACjC,IAAI,EAAE,MAAM,CAAC;IAEb,0DAA0D;IAC1D,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,cAAc,CAAC,cAAc,CAAC,CAAC;IAC1H,+EAA+E;IAC/E,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACtH,uFAAuF;IACvF,UAAU,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,gFAAgF;IAChF,IAAI,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5E,gEAAgE;IAChE,OAAO,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/E,0DAA0D;IAC1D,WAAW,CAAC,UAAU,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IAC/D,gGAAgG;IAChG,SAAS,CAAC,CAAC,aAAa,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,cAAc,EAAE,MAAM,GAAG,cAAc,CAAC,cAAc,CAAC,CAAC;CAC5H;AAED;;;;;;GAMG;AACH,wBAAuB,oBAAoB,CACzC,aAAa,EAAE,MAAM,EACrB,OAAO,EAAE,kBAAkB,EAC3B,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAC/B,cAAc,EAAE,MAAM,EACtB,YAAY,EAAE,OAAO,EACrB,MAAM,GAAE,aAA6B,GACpC,cAAc,CAAC,cAAc,CAAC,CAahC"}
@@ -0,0 +1,24 @@
1
+ import { defaultLogger } from "./logger.js";
2
+ /**
3
+ * Try fast reconnect if the adapter supports it and the environment was
4
+ * previously bootstrapped, falling back to full provision on any error.
5
+ *
6
+ * Yields {@link ProvisionEvent}s from whichever path runs, allowing callers
7
+ * (gRPC streaming and WebSocket broadcast) to forward them uniformly.
8
+ */
9
+ export async function* reconnectOrProvision(environmentId, adapter, config, powerlineToken, bootstrapped, logger = defaultLogger) {
10
+ let reconnected = false;
11
+ if (bootstrapped && adapter.reconnect) {
12
+ try {
13
+ yield* adapter.reconnect(environmentId, config, powerlineToken);
14
+ reconnected = true;
15
+ }
16
+ catch (err) {
17
+ logger.info({ environmentId, err }, "Reconnect failed, falling back to full provision");
18
+ }
19
+ }
20
+ if (!reconnected) {
21
+ yield* adapter.provision(environmentId, config, powerlineToken);
22
+ }
23
+ }
24
+ //# sourceMappingURL=adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"adapter.js","sourceRoot":"","sources":["../src/adapter.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AA+C5C;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,SAAS,CAAC,CAAC,oBAAoB,CACzC,aAAqB,EACrB,OAA2B,EAC3B,MAA+B,EAC/B,cAAsB,EACtB,YAAqB,EACrB,SAAwB,aAAa;IAErC,IAAI,WAAW,GAAG,KAAK,CAAC;IACxB,IAAI,YAAY,IAAI,OAAO,CAAC,SAAS,EAAE,CAAC;QACtC,IAAI,CAAC;YACH,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;YAChE,WAAW,GAAG,IAAI,CAAC;QACrB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,IAAI,CAAC,EAAE,aAAa,EAAE,GAAG,EAAE,EAAE,kDAAkD,CAAC,CAAC;QAC1F,CAAC;IACH,CAAC;IACD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,KAAK,CAAC,CAAC,OAAO,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,EAAE,cAAc,CAAC,CAAC;IAClE,CAAC;AACH,CAAC"}
@@ -0,0 +1,89 @@
1
+ import type { ProvisionEvent } from "./adapter.js";
2
+ import type { RemoteExecutor } from "./remote-executor.js";
3
+ import type { AdapterLogger } from "./logger.js";
4
+ /**
5
+ * Write the environment variable file to the remote PowerLine directory.
6
+ * Used during both initial bootstrap and reconnect (tokens may have rotated).
7
+ */
8
+ export declare function writeRemoteEnvFile(executor: RemoteExecutor, powerlineToken: string, extraEnv?: Record<string, string>, logger?: AdapterLogger): Promise<void>;
9
+ /**
10
+ * Probe whether the remote PowerLine is listening on its port.
11
+ * Throws if the port is not reachable.
12
+ */
13
+ export declare function probeRemotePowerLine(executor: RemoteExecutor): Promise<void>;
14
+ /**
15
+ * Build a shell command that kills the remote PowerLine process.
16
+ * Prefers killing by tracked PID (written at startup) to avoid terminating
17
+ * unrelated services on the same port. Falls back to port-based kill.
18
+ */
19
+ export declare function buildRemoteKillCommand(): string;
20
+ /** Options for {@link startRemotePowerLine}. */
21
+ export interface StartRemotePowerLineOptions {
22
+ /** Additional environment variables forwarded to the remote PowerLine. */
23
+ extraEnv?: Record<string, string>;
24
+ /** Explicit working directory for the PowerLine process. */
25
+ workingDirectory?: string;
26
+ /**
27
+ * Host address to bind the PowerLine to. Defaults to unset (PowerLine's
28
+ * own default, 127.0.0.1). Use "0.0.0.0" for Docker containers where
29
+ * the port is accessed via Docker's port mapping.
30
+ */
31
+ host?: string;
32
+ /**
33
+ * When true, detects `/workspaces/*\/` on the remote host (codespace
34
+ * convention) and uses it as the working directory.
35
+ */
36
+ autoDetectWorkspace?: boolean;
37
+ /**
38
+ * When true, the compound script starts with a TCP probe and exits
39
+ * immediately if PowerLine is already listening. This avoids a separate
40
+ * SSH round trip for the initial health check.
41
+ */
42
+ probeFirst?: boolean;
43
+ /** Logger for diagnostic output. */
44
+ logger?: AdapterLogger;
45
+ }
46
+ /**
47
+ * Ensure the remote PowerLine process is running.
48
+ *
49
+ * Batches env-var write, process start, and port probe into a **single SSH
50
+ * call** to minimize per-call latency (each `gh codespace ssh` round trip
51
+ * takes ~10-15 s through GitHub's relay).
52
+ *
53
+ * Uses Node's `spawn({ detached: true })` to properly daemonize the
54
+ * PowerLine process, avoiding the SSH-hanging issue where `nohup ... &`
55
+ * keeps the session alive through GitHub's codespace relay.
56
+ *
57
+ * When `probeFirst` is true the script begins with a TCP port check and
58
+ * returns immediately if PowerLine is already listening, combining the
59
+ * "is it alive?" check and the "start if not" logic into one SSH call.
60
+ *
61
+ * This is the "restart" middle path — it assumes code is already installed
62
+ * and skips npm install, git checks, and artifact copies.
63
+ */
64
+ export declare function startRemotePowerLine(executor: RemoteExecutor, powerlineToken: string, options?: StartRemotePowerLineOptions): Promise<{
65
+ alreadyRunning: boolean;
66
+ }>;
67
+ /** Options for {@link bootstrapPowerLine}. */
68
+ export interface BootstrapOptions {
69
+ /** Additional environment variables forwarded to the remote PowerLine. */
70
+ extraEnv?: Record<string, string>;
71
+ /** Explicit working directory for the PowerLine process. */
72
+ workingDirectory?: string;
73
+ /**
74
+ * Host address to bind the PowerLine to. Defaults to unset (PowerLine's
75
+ * own default, 127.0.0.1). Use "0.0.0.0" for Docker containers where
76
+ * the port is accessed via Docker's port mapping.
77
+ */
78
+ host?: string;
79
+ /** Logger for diagnostic output. */
80
+ logger?: AdapterLogger;
81
+ /** Callback to check whether the GitHub credential provider is enabled. */
82
+ isGitHubProviderEnabled?: () => boolean;
83
+ }
84
+ /**
85
+ * Bootstrap the PowerLine on a remote host via the given executor.
86
+ * Yields progress events for each stage of the process.
87
+ */
88
+ export declare function bootstrapPowerLine(executor: RemoteExecutor, powerlineToken: string, options?: BootstrapOptions): AsyncGenerator<ProvisionEvent>;
89
+ //# sourceMappingURL=bootstrap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.d.ts","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AACnD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAoDjD;;;GAGG;AACH,wBAAsB,kBAAkB,CACtC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,EACjC,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CAef;AAUD;;;GAGG;AACH,wBAAsB,oBAAoB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAElF;AAID;;;;GAIG;AACH,wBAAgB,sBAAsB,IAAI,MAAM,CAmB/C;AAID,gDAAgD;AAChD,MAAM,WAAW,2BAA2B;IAC1C,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;OAGG;IACH,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B;;;;OAIG;IACH,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,oCAAoC;IACpC,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAoBD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE,2BAAgC,GACxC,OAAO,CAAC;IAAE,cAAc,EAAE,OAAO,CAAA;CAAE,CAAC,CA0FtC;AAID,8CAA8C;AAC9C,MAAM,WAAW,gBAAgB;IAC/B,0EAA0E;IAC1E,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAClC,4DAA4D;IAC5D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,MAAM,CAAC,EAAE,aAAa,CAAC;IACvB,2EAA2E;IAC3E,uBAAuB,CAAC,EAAE,MAAM,OAAO,CAAC;CACzC;AAED;;;GAGG;AACH,wBAAuB,kBAAkB,CACvC,QAAQ,EAAE,cAAc,EACxB,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE,gBAAqB,GAC7B,cAAc,CAAC,cAAc,CAAC,CAyLhC"}
@@ -0,0 +1,322 @@
1
+ import { DEFAULT_POWERLINE_PORT } from "@grackle-ai/common";
2
+ import { defaultLogger } from "./logger.js";
3
+ import { REMOTE_POWERLINE_DIRECTORY, REMOTE_EXEC_DEFAULT_TIMEOUT_MS, SSH_CONNECTIVITY_TIMEOUT_MS, sleep, isDevMode, getPackageVersion, shellEscape, } from "./utils.js";
4
+ import { readFileSync } from "node:fs";
5
+ import { join, resolve } from "node:path";
6
+ // ─── Constants ──────────────────────────────────────────────
7
+ /** Timeout for `npm install` on the remote host. */
8
+ const BOOTSTRAP_NPM_INSTALL_TIMEOUT_MS = 120_000;
9
+ /** Wait after starting the remote PowerLine process before verifying. */
10
+ const POWERLINE_STARTUP_DELAY_MS = 2_000;
11
+ // ─── Env File Helpers ───────────────────────────────────────
12
+ /** Regex for valid POSIX environment variable names. */
13
+ const ENV_VAR_NAME_PATTERN = /^[A-Za-z_][A-Za-z0-9_]*$/;
14
+ /**
15
+ * Build the list of env-file lines for the PowerLine process.
16
+ * Pure helper used by both {@link writeRemoteEnvFile} and {@link startRemotePowerLine}.
17
+ */
18
+ function writeRemoteEnvFileLines(powerlineToken, extraEnv, logger = defaultLogger) {
19
+ const envLines = [];
20
+ if (powerlineToken) {
21
+ envLines.push(`export GRACKLE_POWERLINE_TOKEN='${shellEscape(powerlineToken)}'`);
22
+ }
23
+ if (extraEnv) {
24
+ for (const [key, value] of Object.entries(extraEnv)) {
25
+ if (!ENV_VAR_NAME_PATTERN.test(key)) {
26
+ logger.warn({ key }, "Skipping invalid env var name");
27
+ continue;
28
+ }
29
+ envLines.push(`export ${key}='${shellEscape(value)}'`);
30
+ }
31
+ }
32
+ return envLines;
33
+ }
34
+ /**
35
+ * Write the environment variable file to the remote PowerLine directory.
36
+ * Used during both initial bootstrap and reconnect (tokens may have rotated).
37
+ */
38
+ export async function writeRemoteEnvFile(executor, powerlineToken, extraEnv, logger = defaultLogger) {
39
+ const envLines = writeRemoteEnvFileLines(powerlineToken, extraEnv, logger);
40
+ if (envLines.length === 0) {
41
+ return;
42
+ }
43
+ const envFileContent = envLines.join("\n") + "\n";
44
+ const envFileContentBase64 = Buffer.from(envFileContent, "utf8").toString("base64");
45
+ await executor.exec(`cd ${REMOTE_POWERLINE_DIRECTORY} && node -e "require('fs').writeFileSync('.env.sh',Buffer.from(process.argv[1],'base64').toString('utf8'))" '${shellEscape(envFileContentBase64)}'`, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
46
+ await executor.exec(`chmod 600 ${REMOTE_POWERLINE_DIRECTORY}/.env.sh`, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
47
+ }
48
+ // ─── Remote Probe ───────────────────────────────────────────
49
+ /** Node.js one-liner that probes the PowerLine port and exits 0/1. */
50
+ const PROBE_SCRIPT = `node -e "const s=require('net').createConnection(${DEFAULT_POWERLINE_PORT},'127.0.0.1');`
51
+ + `s.on('connect',()=>{s.destroy();process.exit(0)});`
52
+ + `s.on('error',()=>process.exit(1))"`;
53
+ /**
54
+ * Probe whether the remote PowerLine is listening on its port.
55
+ * Throws if the port is not reachable.
56
+ */
57
+ export async function probeRemotePowerLine(executor) {
58
+ await executor.exec(PROBE_SCRIPT, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
59
+ }
60
+ // ─── Remote Kill ────────────────────────────────────────────
61
+ /**
62
+ * Build a shell command that kills the remote PowerLine process.
63
+ * Prefers killing by tracked PID (written at startup) to avoid terminating
64
+ * unrelated services on the same port. Falls back to port-based kill.
65
+ */
66
+ export function buildRemoteKillCommand() {
67
+ const pidfile = `${REMOTE_POWERLINE_DIRECTORY}/powerline.pid`;
68
+ // Try pidfile-based kill first (safe — only kills what we started)
69
+ const pidfileKill = [
70
+ `[ -f "${pidfile}" ]`,
71
+ `PID=$(cat "${pidfile}" 2>/dev/null)`,
72
+ `[ -n "$PID" ]`,
73
+ `kill "$PID" 2>/dev/null`,
74
+ `rm -f "${pidfile}"`,
75
+ ].join(" && ");
76
+ // Fallback: port-based kill (for upgrades from before pidfile support)
77
+ const portKill = `fuser -k ${DEFAULT_POWERLINE_PORT}/tcp 2>/dev/null`
78
+ + ` || lsof -ti:${DEFAULT_POWERLINE_PORT} | xargs kill 2>/dev/null`
79
+ + ` || pkill -f "powerline.*${DEFAULT_POWERLINE_PORT}" 2>/dev/null`;
80
+ return `(${pidfileKill}) || (${portKill}) || true`;
81
+ }
82
+ /** Validate and build the node one-liner that spawns a fully detached PowerLine process. */
83
+ function buildSpawnScript(host) {
84
+ if (host && !/^[\d.a-zA-Z:]+$/.test(host)) {
85
+ throw new Error(`Invalid host address: ${host}`);
86
+ }
87
+ const hostArg = host ? `,'--host=${host}'` : "";
88
+ return (`node -e "`
89
+ + `const fs=require('fs');`
90
+ + `const {spawn}=require('child_process');`
91
+ + `const out=fs.openSync(process.argv[3],'w');`
92
+ + `const c=spawn('node',[process.argv[1],'--port=${DEFAULT_POWERLINE_PORT}'${hostArg}],`
93
+ + `{cwd:process.cwd(),detached:true,stdio:['ignore',out,out]});`
94
+ + `fs.writeFileSync(process.argv[2],String(c.pid));`
95
+ + `c.unref();"`);
96
+ }
97
+ /**
98
+ * Ensure the remote PowerLine process is running.
99
+ *
100
+ * Batches env-var write, process start, and port probe into a **single SSH
101
+ * call** to minimize per-call latency (each `gh codespace ssh` round trip
102
+ * takes ~10-15 s through GitHub's relay).
103
+ *
104
+ * Uses Node's `spawn({ detached: true })` to properly daemonize the
105
+ * PowerLine process, avoiding the SSH-hanging issue where `nohup ... &`
106
+ * keeps the session alive through GitHub's codespace relay.
107
+ *
108
+ * When `probeFirst` is true the script begins with a TCP port check and
109
+ * returns immediately if PowerLine is already listening, combining the
110
+ * "is it alive?" check and the "start if not" logic into one SSH call.
111
+ *
112
+ * This is the "restart" middle path — it assumes code is already installed
113
+ * and skips npm install, git checks, and artifact copies.
114
+ */
115
+ export async function startRemotePowerLine(executor, powerlineToken, options = {}) {
116
+ const { extraEnv, workingDirectory, host, autoDetectWorkspace, probeFirst, logger = defaultLogger } = options;
117
+ // Validate workingDirectory to prevent shell injection — must be an absolute POSIX path
118
+ if (workingDirectory && !/^\/[\w./-]+$/.test(workingDirectory)) {
119
+ throw new Error(`Invalid working directory: ${workingDirectory}`);
120
+ }
121
+ const envLines = writeRemoteEnvFileLines(powerlineToken, extraEnv, logger);
122
+ const devMode = isDevMode();
123
+ const entryPoint = devMode
124
+ ? "dist/index.js"
125
+ : "node_modules/@grackle-ai/powerline/dist/index.js";
126
+ const absoluteEntryPoint = `${REMOTE_POWERLINE_DIRECTORY}/${entryPoint}`;
127
+ const logFilePath = "$HOME/.grackle/powerline.log";
128
+ const pidFilePath = `${REMOTE_POWERLINE_DIRECTORY}/powerline.pid`;
129
+ // Build a compound script that runs in a single SSH call:
130
+ // 0. (Optional) Probe — exit early if already listening
131
+ // 1. Write env file (base64 → file)
132
+ // 2. Detect working directory (optional)
133
+ // 3. Source env + spawn PowerLine (detached, exits immediately)
134
+ // 4. Brief sleep + probe
135
+ const parts = [];
136
+ // 0. Early-exit probe (saves work when PowerLine is already running).
137
+ let probeFirstPrefix = "";
138
+ if (probeFirst) {
139
+ probeFirstPrefix = `${PROBE_SCRIPT} && echo "__PL_ALIVE__" && exit 0; `;
140
+ }
141
+ // 1. Env file
142
+ if (envLines.length > 0) {
143
+ const envFileContent = envLines.join("\n") + "\n";
144
+ const envFileContentBase64 = Buffer.from(envFileContent, "utf8").toString("base64");
145
+ parts.push(`cd ${REMOTE_POWERLINE_DIRECTORY}`
146
+ + ` && node -e "require('fs').writeFileSync('.env.sh',Buffer.from(process.argv[1],'base64').toString('utf8'))"`
147
+ + ` '${shellEscape(envFileContentBase64)}'`
148
+ + ` && chmod 600 .env.sh`);
149
+ }
150
+ // 2. Working directory
151
+ let startDirExpr;
152
+ if (workingDirectory) {
153
+ startDirExpr = workingDirectory;
154
+ }
155
+ else if (autoDetectWorkspace) {
156
+ parts.push(`WD=$(ls -d /workspaces/*/ 2>/dev/null | head -1 | sed "s/\\/$//");`
157
+ + ` WD=\${WD:-${REMOTE_POWERLINE_DIRECTORY}}`);
158
+ startDirExpr = "$WD";
159
+ }
160
+ else {
161
+ startDirExpr = REMOTE_POWERLINE_DIRECTORY;
162
+ }
163
+ // 3. Source env vars and spawn PowerLine as a detached process.
164
+ const sourceEnv = envLines.length > 0
165
+ ? `. ${REMOTE_POWERLINE_DIRECTORY}/.env.sh && `
166
+ : "";
167
+ parts.push(`cd "${startDirExpr}" && ${sourceEnv}`
168
+ + `${buildSpawnScript(host)} "${absoluteEntryPoint}" "${pidFilePath}" "${logFilePath}"`);
169
+ // 4. Probe (after a brief pause for the port to bind)
170
+ parts.push(`sleep ${POWERLINE_STARTUP_DELAY_MS / 1000} && ${PROBE_SCRIPT}`);
171
+ const compoundScript = probeFirstPrefix + parts.join(" && ");
172
+ try {
173
+ const stdout = await executor.exec(`bash -c '${shellEscape(compoundScript)}'`, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
174
+ if (probeFirst && stdout.includes("__PL_ALIVE__")) {
175
+ logger.info({ port: DEFAULT_POWERLINE_PORT }, "Remote PowerLine was already running");
176
+ return { alreadyRunning: true };
177
+ }
178
+ logger.info({ port: DEFAULT_POWERLINE_PORT }, "Remote PowerLine is listening");
179
+ return { alreadyRunning: false };
180
+ }
181
+ catch (err) {
182
+ const detail = err instanceof Error ? err.message : String(err);
183
+ logger.info({ detail }, "Failed to start remote PowerLine");
184
+ throw new Error(`PowerLine process died immediately after starting. Check ~/.grackle/powerline.log on the remote host. Cause: ${detail}`);
185
+ }
186
+ }
187
+ /**
188
+ * Bootstrap the PowerLine on a remote host via the given executor.
189
+ * Yields progress events for each stage of the process.
190
+ */
191
+ export async function* bootstrapPowerLine(executor, powerlineToken, options = {}) {
192
+ const { extraEnv, workingDirectory, host, logger = defaultLogger, isGitHubProviderEnabled, } = options;
193
+ // 1. Check Node.js (PowerLine requires >= 22)
194
+ yield { stage: "bootstrapping", message: "Checking Node.js on remote host...", progress: 0.10 };
195
+ try {
196
+ const nodeVersionOutput = await executor.exec("node --version", { timeout: SSH_CONNECTIVITY_TIMEOUT_MS });
197
+ const nodeVersion = String(nodeVersionOutput).trim();
198
+ logger.info({ nodeVersion }, "Remote Node.js version");
199
+ const versionMatch = nodeVersion.match(/^v?(\d+)\./);
200
+ if (!versionMatch) {
201
+ throw new Error(`Unable to parse Node.js version "${nodeVersion}" on remote host. Install Node.js >= 22 and try again.`);
202
+ }
203
+ const majorVersion = parseInt(versionMatch[1], 10);
204
+ if (isNaN(majorVersion) || majorVersion < 22) {
205
+ throw new Error(`Unsupported Node.js version "${nodeVersion}" on remote host. PowerLine requires Node.js >= 22.`);
206
+ }
207
+ }
208
+ catch (error) {
209
+ if (error instanceof Error
210
+ && (error.message.startsWith("Unable to parse Node.js version")
211
+ || error.message.startsWith("Unsupported Node.js version"))) {
212
+ throw error;
213
+ }
214
+ throw new Error("Node.js is not installed or not accessible on the remote host. Install Node.js >= 22 and try again.");
215
+ }
216
+ // 2. Check git
217
+ yield { stage: "bootstrapping", message: "Checking git on remote host...", progress: 0.15 };
218
+ try {
219
+ await executor.exec("git --version", { timeout: SSH_CONNECTIVITY_TIMEOUT_MS });
220
+ }
221
+ catch {
222
+ throw new Error("git is not installed on the remote host. Install git and try again.");
223
+ }
224
+ // 2.5. Capture GITHUB_TOKEN from remote host for git push connectivity.
225
+ let enrichedExtraEnv = extraEnv;
226
+ const hasLocalToken = process.env.GITHUB_TOKEN || process.env.GH_TOKEN;
227
+ const hasAdapterToken = extraEnv?.GITHUB_TOKEN || extraEnv?.GH_TOKEN;
228
+ const githubProviderEnabled = isGitHubProviderEnabled ? isGitHubProviderEnabled() : false;
229
+ if (githubProviderEnabled && !hasLocalToken && !hasAdapterToken) {
230
+ try {
231
+ const remoteToken = (await executor.exec(`(grep -m1 '^GITHUB_TOKEN=' /workspaces/.codespaces/shared/.env 2>/dev/null | cut -d= -f2- || grep -m1 '^GH_TOKEN=' /workspaces/.codespaces/shared/.env 2>/dev/null | cut -d= -f2- || printenv GITHUB_TOKEN 2>/dev/null || printenv GH_TOKEN 2>/dev/null || true)`, { timeout: SSH_CONNECTIVITY_TIMEOUT_MS })).trim();
232
+ if (remoteToken) {
233
+ enrichedExtraEnv = { ...extraEnv, GITHUB_TOKEN: remoteToken };
234
+ logger.info({}, "Captured GITHUB_TOKEN from remote host for agent git operations");
235
+ }
236
+ }
237
+ catch {
238
+ logger.debug({}, "Could not read GITHUB_TOKEN from remote host");
239
+ }
240
+ }
241
+ // 3. Install PowerLine — dev mode (copy artifacts) vs production (npm install)
242
+ const devMode = isDevMode();
243
+ if (devMode) {
244
+ // ── Dev mode: copy local monorepo artifacts ──
245
+ yield { stage: "bootstrapping", message: "Creating remote directories...", progress: 0.20 };
246
+ await executor.exec(`mkdir -p ${REMOTE_POWERLINE_DIRECTORY}/node_modules/@grackle-ai/common`, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
247
+ // Resolve local artifact paths relative to adapter-sdk's built location.
248
+ // import.meta.dirname = packages/adapter-sdk/dist → up 2 levels → packages/
249
+ const sdkDistDir = resolve(import.meta.dirname);
250
+ const commonPackageDir = resolve(sdkDistDir, "../../common");
251
+ const powerlinePackageDir = resolve(sdkDistDir, "../../powerline");
252
+ const mcpPackageDir = resolve(sdkDistDir, "../../mcp");
253
+ yield { stage: "bootstrapping", message: "Copying PowerLine artifacts...", progress: 0.25 };
254
+ await executor.copyTo(join(powerlinePackageDir, "dist"), `${REMOTE_POWERLINE_DIRECTORY}/dist`);
255
+ await executor.copyTo(join(powerlinePackageDir, "package.json"), `${REMOTE_POWERLINE_DIRECTORY}/package.json`);
256
+ // Collect non-workspace deps from @grackle-ai/common and @grackle-ai/mcp
257
+ const extraDeps = {};
258
+ for (const dir of [commonPackageDir, mcpPackageDir]) {
259
+ const pkg = JSON.parse(readFileSync(join(dir, "package.json"), "utf8"));
260
+ for (const [k, v] of Object.entries(pkg.dependencies || {})) {
261
+ if (!k.startsWith("@grackle-ai/")) {
262
+ extraDeps[k] = v;
263
+ }
264
+ }
265
+ }
266
+ // Strip @grackle-ai/* workspace deps, merge in common/mcp deps, then npm install once.
267
+ yield { stage: "bootstrapping", message: "Installing dependencies on remote host...", progress: 0.40 };
268
+ const extraDepsJson = JSON.stringify(extraDeps).replace(/'/g, "'\\''");
269
+ await executor.exec(`cd ${REMOTE_POWERLINE_DIRECTORY} && node -e "`
270
+ + `const p=JSON.parse(require('fs').readFileSync('package.json','utf8'));`
271
+ + `for(const k of Object.keys(p.dependencies||{})){if(k.startsWith('@grackle-ai/'))delete p.dependencies[k];}`
272
+ + `for(const k of Object.keys(p.devDependencies||{})){if(k.startsWith('@grackle-ai/'))delete p.devDependencies[k];}`
273
+ + `Object.assign(p.dependencies||{},JSON.parse(process.argv[1]));`
274
+ + `require('fs').writeFileSync('package.json',JSON.stringify(p,null,2));" '${extraDepsJson}'`, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
275
+ await executor.exec(`cd ${REMOTE_POWERLINE_DIRECTORY} && npm install --omit=dev --legacy-peer-deps --registry=https://registry.npmjs.org`, { timeout: BOOTSTRAP_NPM_INSTALL_TIMEOUT_MS });
276
+ // Copy @grackle-ai/* packages AFTER all npm installs (npm wipes unmanaged dirs)
277
+ for (const [name, dir] of [["common", commonPackageDir], ["mcp", mcpPackageDir]]) {
278
+ const remotePkgDir = `${REMOTE_POWERLINE_DIRECTORY}/node_modules/@grackle-ai/${name}`;
279
+ yield { stage: "bootstrapping", message: `Copying @grackle-ai/${name}...`, progress: name === "common" ? 0.57 : 0.59 };
280
+ await executor.exec(`mkdir -p ${remotePkgDir}`, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
281
+ await executor.copyTo(join(dir, "dist"), `${remotePkgDir}/dist`);
282
+ await executor.copyTo(join(dir, "package.json"), `${remotePkgDir}/package.json`);
283
+ }
284
+ }
285
+ else {
286
+ // ── Production mode: npm install from registry ──
287
+ const version = getPackageVersion();
288
+ yield { stage: "bootstrapping", message: "Creating remote directories...", progress: 0.20 };
289
+ await executor.exec(`mkdir -p ${REMOTE_POWERLINE_DIRECTORY}`, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
290
+ yield { stage: "bootstrapping", message: `Installing @grackle-ai/powerline@${version}...`, progress: 0.25 };
291
+ await executor.exec(`cd ${REMOTE_POWERLINE_DIRECTORY} && npm init -y && npm install @grackle-ai/powerline@${version} --omit=dev --legacy-peer-deps --registry=https://registry.npmjs.org`, { timeout: BOOTSTRAP_NPM_INSTALL_TIMEOUT_MS });
292
+ }
293
+ logger.info({ devMode }, "PowerLine bootstrap mode");
294
+ // 4. Configure git credential helper so agents can push to GitHub.
295
+ yield { stage: "bootstrapping", message: "Configuring git credentials...", progress: 0.56 };
296
+ try {
297
+ const credHelperScript = '#!/bin/sh\ntest "$1" = get || exit 0\necho "username=x-access-token"\necho "password=${GITHUB_TOKEN:-$GH_TOKEN}"\n';
298
+ const credHelperBase64 = Buffer.from(credHelperScript, "utf8").toString("base64");
299
+ const credHelperPath = `${REMOTE_POWERLINE_DIRECTORY}/git-credential-github.sh`;
300
+ await executor.exec(`node -e "require('fs').writeFileSync(process.argv[1],Buffer.from(process.argv[2],'base64').toString('utf8'),{mode:0o755})" `
301
+ + `"${credHelperPath}" '${credHelperBase64}'`
302
+ + ` && git config --global credential.helper "${credHelperPath}"`, { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
303
+ logger.info({ path: credHelperPath }, "Git credential helper configured");
304
+ }
305
+ catch (err) {
306
+ logger.warn({ err }, "Failed to configure git credential helper (agents may be unable to push)");
307
+ }
308
+ // 5. Kill any existing PowerLine process on the port (with fallbacks)
309
+ yield { stage: "bootstrapping", message: "Stopping any existing PowerLine process...", progress: 0.60 };
310
+ try {
311
+ await executor.exec(buildRemoteKillCommand(), { timeout: REMOTE_EXEC_DEFAULT_TIMEOUT_MS });
312
+ await sleep(1_000);
313
+ }
314
+ catch {
315
+ // Ignore — no process to kill
316
+ }
317
+ // 6–8. Write env vars, start process, wait, verify
318
+ yield { stage: "bootstrapping", message: "Starting PowerLine on remote host...", progress: 0.65 };
319
+ await startRemotePowerLine(executor, powerlineToken, { extraEnv: enrichedExtraEnv, workingDirectory, host, logger });
320
+ yield { stage: "bootstrapping", message: "PowerLine is running on remote host", progress: 0.75 };
321
+ }
322
+ //# sourceMappingURL=bootstrap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bootstrap.js","sourceRoot":"","sources":["../src/bootstrap.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,18 @@
1
+ import type { PowerLineClient, PowerLineConnection } from "./adapter.js";
2
+ import type { AdapterLogger } from "./logger.js";
3
+ /**
4
+ * Create an authenticated gRPC client for a PowerLine.
5
+ * The PowerLine token is sent as a Bearer token on every request.
6
+ */
7
+ export declare function createPowerLineClient(baseUrl: string, powerlineToken: string): PowerLineClient;
8
+ /**
9
+ * Connect to a PowerLine through a local tunnel port, retrying until the gRPC
10
+ * service responds to a ping.
11
+ */
12
+ export declare function connectThroughTunnel(environmentId: string, localPort: number, powerlineToken: string, logger?: AdapterLogger): Promise<PowerLineConnection>;
13
+ /**
14
+ * Poll until a TCP connection can be established on localhost at the given port.
15
+ * Used to wait for a tunnel process to begin accepting connections.
16
+ */
17
+ export declare function waitForLocalPort(port: number): Promise<void>;
18
+ //# sourceMappingURL=connect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.d.ts","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,eAAe,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AAGzE,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAmBjD;;;GAGG;AACH,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,MAAM,GAAG,eAAe,CAa9F;AAID;;;GAGG;AACH,wBAAsB,oBAAoB,CACxC,aAAa,EAAE,MAAM,EACrB,SAAS,EAAE,MAAM,EACjB,cAAc,EAAE,MAAM,EACtB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,mBAAmB,CAAC,CAsB9B;AAID;;;GAGG;AACH,wBAAsB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBlE"}
@@ -0,0 +1,88 @@
1
+ import { createClient } from "@connectrpc/connect";
2
+ import { createGrpcTransport } from "@connectrpc/connect-node";
3
+ import { powerline } from "@grackle-ai/common";
4
+ import { createConnection } from "node:net";
5
+ import { closeTunnel } from "./tunnel-registry.js";
6
+ import { sleep } from "./utils.js";
7
+ import { defaultLogger } from "./logger.js";
8
+ // ─── Constants ──────────────────────────────────────────────
9
+ /** Delay between gRPC connect-with-retry attempts. */
10
+ const CONNECT_RETRY_DELAY_MS = 1_500;
11
+ /** Maximum number of gRPC connect-with-retry attempts. */
12
+ const CONNECT_MAX_RETRIES = 10;
13
+ /** Delay between port availability polls. */
14
+ const TUNNEL_PORT_POLL_DELAY_MS = 500;
15
+ /** Maximum number of port availability polls. */
16
+ const TUNNEL_PORT_POLL_MAX_ATTEMPTS = 20;
17
+ // ─── PowerLine Client ───────────────────────────────────────
18
+ /**
19
+ * Create an authenticated gRPC client for a PowerLine.
20
+ * The PowerLine token is sent as a Bearer token on every request.
21
+ */
22
+ export function createPowerLineClient(baseUrl, powerlineToken) {
23
+ const transport = createGrpcTransport({
24
+ baseUrl,
25
+ interceptors: powerlineToken
26
+ ? [
27
+ (next) => async (req) => {
28
+ req.header.set("Authorization", `Bearer ${powerlineToken}`);
29
+ return next(req);
30
+ },
31
+ ]
32
+ : [],
33
+ });
34
+ return createClient(powerline.GracklePowerLine, transport);
35
+ }
36
+ // ─── Connect Through Tunnel ─────────────────────────────────
37
+ /**
38
+ * Connect to a PowerLine through a local tunnel port, retrying until the gRPC
39
+ * service responds to a ping.
40
+ */
41
+ export async function connectThroughTunnel(environmentId, localPort, powerlineToken, logger = defaultLogger) {
42
+ const client = createPowerLineClient(`http://127.0.0.1:${localPort}`, powerlineToken);
43
+ let lastError;
44
+ for (let attempt = 0; attempt < CONNECT_MAX_RETRIES; attempt++) {
45
+ try {
46
+ await client.ping({});
47
+ return { client, environmentId, port: localPort };
48
+ }
49
+ catch (err) {
50
+ lastError = err;
51
+ await sleep(CONNECT_RETRY_DELAY_MS);
52
+ }
53
+ }
54
+ // Clean up the tunnel so we don't leak background processes on connect failure
55
+ try {
56
+ await closeTunnel(environmentId);
57
+ }
58
+ catch (err) {
59
+ logger.error({ environmentId, err }, "Failed to close tunnel after connect failure");
60
+ }
61
+ throw new Error(`Could not reach PowerLine after ${CONNECT_MAX_RETRIES} attempts: ${lastError instanceof Error ? lastError.message : String(lastError)}`);
62
+ }
63
+ // ─── Wait for Local Port ────────────────────────────────────
64
+ /**
65
+ * Poll until a TCP connection can be established on localhost at the given port.
66
+ * Used to wait for a tunnel process to begin accepting connections.
67
+ */
68
+ export async function waitForLocalPort(port) {
69
+ for (let attempt = 0; attempt < TUNNEL_PORT_POLL_MAX_ATTEMPTS; attempt++) {
70
+ const reachable = await new Promise((resolve) => {
71
+ const socket = createConnection({ host: "127.0.0.1", port });
72
+ socket.once("connect", () => {
73
+ socket.destroy();
74
+ resolve(true);
75
+ });
76
+ socket.once("error", () => {
77
+ socket.destroy();
78
+ resolve(false);
79
+ });
80
+ });
81
+ if (reachable) {
82
+ return;
83
+ }
84
+ await sleep(TUNNEL_PORT_POLL_DELAY_MS);
85
+ }
86
+ throw new Error(`Local port ${port} did not become reachable after ${TUNNEL_PORT_POLL_MAX_ATTEMPTS} attempts`);
87
+ }
88
+ //# sourceMappingURL=connect.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"connect.js","sourceRoot":"","sources":["../src/connect.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export type { AdapterLogger } from "./logger.js";
2
+ export { defaultLogger } from "./logger.js";
3
+ export type { PowerLineClient, PowerLineConnection, ProvisionEvent, BaseEnvironmentConfig, EnvironmentAdapter, } from "./adapter.js";
4
+ export { reconnectOrProvision } from "./adapter.js";
5
+ export type { RemoteExecutor } from "./remote-executor.js";
6
+ export type { RemoteTunnel } from "./tunnel.js";
7
+ export { ProcessTunnel } from "./tunnel.js";
8
+ export type { TunnelState } from "./tunnel-registry.js";
9
+ export { registerTunnel, getTunnel, closeTunnel, closeAllTunnels } from "./tunnel-registry.js";
10
+ export { createPowerLineClient, connectThroughTunnel, waitForLocalPort } from "./connect.js";
11
+ export type { BootstrapOptions, StartRemotePowerLineOptions } from "./bootstrap.js";
12
+ export { bootstrapPowerLine, startRemotePowerLine, probeRemotePowerLine, writeRemoteEnvFile, buildRemoteKillCommand, } from "./bootstrap.js";
13
+ export { remoteStop, remoteDestroy, remoteHealthCheck } from "./shared-operations.js";
14
+ export { sleep, findFreePort, isDevMode, getPackageVersion, shellEscape, REMOTE_POWERLINE_DIRECTORY, SSH_CONNECTIVITY_TIMEOUT_MS, REMOTE_EXEC_DEFAULT_TIMEOUT_MS, } from "./utils.js";
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AACjD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAG5C,YAAY,EACV,eAAe,EACf,mBAAmB,EACnB,cAAc,EACd,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAGpD,YAAY,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAG3D,YAAY,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,YAAY,EAAE,WAAW,EAAE,MAAM,sBAAsB,CAAC;AACxD,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAG/F,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAG7F,YAAY,EAAE,gBAAgB,EAAE,2BAA2B,EAAE,MAAM,gBAAgB,CAAC;AACpF,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAGtF,OAAO,EACL,KAAK,EACL,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,YAAY,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,12 @@
1
+ export { defaultLogger } from "./logger.js";
2
+ export { reconnectOrProvision } from "./adapter.js";
3
+ export { ProcessTunnel } from "./tunnel.js";
4
+ export { registerTunnel, getTunnel, closeTunnel, closeAllTunnels } from "./tunnel-registry.js";
5
+ // ─── Connect ────────────────────────────────────────────────
6
+ export { createPowerLineClient, connectThroughTunnel, waitForLocalPort } from "./connect.js";
7
+ export { bootstrapPowerLine, startRemotePowerLine, probeRemotePowerLine, writeRemoteEnvFile, buildRemoteKillCommand, } from "./bootstrap.js";
8
+ // ─── Shared Operations ─────────────────────────────────────
9
+ export { remoteStop, remoteDestroy, remoteHealthCheck } from "./shared-operations.js";
10
+ // ─── Utilities ──────────────────────────────────────────────
11
+ export { sleep, findFreePort, isDevMode, getPackageVersion, shellEscape, REMOTE_POWERLINE_DIRECTORY, SSH_CONNECTIVITY_TIMEOUT_MS, REMOTE_EXEC_DEFAULT_TIMEOUT_MS, } from "./utils.js";
12
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAU5C,OAAO,EAAE,oBAAoB,EAAE,MAAM,cAAc,CAAC;AAOpD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,WAAW,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AAE/F,+DAA+D;AAC/D,OAAO,EAAE,qBAAqB,EAAE,oBAAoB,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAI7F,OAAO,EACL,kBAAkB,EAClB,oBAAoB,EACpB,oBAAoB,EACpB,kBAAkB,EAClB,sBAAsB,GACvB,MAAM,gBAAgB,CAAC;AAExB,8DAA8D;AAC9D,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEtF,+DAA+D;AAC/D,OAAO,EACL,KAAK,EACL,YAAY,EACZ,SAAS,EACT,iBAAiB,EACjB,WAAW,EACX,0BAA0B,EAC1B,2BAA2B,EAC3B,8BAA8B,GAC/B,MAAM,YAAY,CAAC"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Logger interface compatible with pino's structured-logging signature.
3
+ * All SDK functions accept an optional {@link AdapterLogger} parameter;
4
+ * when omitted, the {@link defaultLogger} (console) is used.
5
+ */
6
+ export interface AdapterLogger {
7
+ info(obj: object, msg: string): void;
8
+ warn(obj: object, msg: string): void;
9
+ error(obj: object, msg: string): void;
10
+ debug(obj: object, msg: string): void;
11
+ }
12
+ /** Console-based logger used when no logger is explicitly provided. */
13
+ export declare const defaultLogger: AdapterLogger;
14
+ //# sourceMappingURL=logger.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.d.ts","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,MAAM,WAAW,aAAa;IAC5B,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,IAAI,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACrC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACtC,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;CACvC;AAED,uEAAuE;AACvE,eAAO,MAAM,aAAa,EAAE,aAa3B,CAAC"}
package/dist/logger.js ADDED
@@ -0,0 +1,16 @@
1
+ /** Console-based logger used when no logger is explicitly provided. */
2
+ export const defaultLogger = {
3
+ info(_obj, msg) {
4
+ console.log("[adapter-sdk]", msg);
5
+ },
6
+ warn(_obj, msg) {
7
+ console.warn("[adapter-sdk]", msg);
8
+ },
9
+ error(_obj, msg) {
10
+ console.error("[adapter-sdk]", msg);
11
+ },
12
+ debug(_obj, msg) {
13
+ console.debug("[adapter-sdk]", msg);
14
+ },
15
+ };
16
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../src/logger.ts"],"names":[],"mappings":"AAYA,uEAAuE;AACvE,MAAM,CAAC,MAAM,aAAa,GAAkB;IAC1C,IAAI,CAAC,IAAY,EAAE,GAAW;QAC5B,OAAO,CAAC,GAAG,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACpC,CAAC;IACD,IAAI,CAAC,IAAY,EAAE,GAAW;QAC5B,OAAO,CAAC,IAAI,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACrC,CAAC;IACD,KAAK,CAAC,IAAY,EAAE,GAAW;QAC7B,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC;IACD,KAAK,CAAC,IAAY,EAAE,GAAW;QAC7B,OAAO,CAAC,KAAK,CAAC,eAAe,EAAE,GAAG,CAAC,CAAC;IACtC,CAAC;CACF,CAAC"}
@@ -0,0 +1,10 @@
1
+ /** Abstraction for executing commands on a remote host. */
2
+ export interface RemoteExecutor {
3
+ /** Execute a shell command on the remote host and return stdout. */
4
+ exec(command: string, opts?: {
5
+ timeout?: number;
6
+ }): Promise<string>;
7
+ /** Copy a local file or directory to a path on the remote host. */
8
+ copyTo(localPath: string, remotePath: string): Promise<void>;
9
+ }
10
+ //# sourceMappingURL=remote-executor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-executor.d.ts","sourceRoot":"","sources":["../src/remote-executor.ts"],"names":[],"mappings":"AAAA,2DAA2D;AAC3D,MAAM,WAAW,cAAc;IAC7B,oEAAoE;IACpE,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE;QAAE,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAAC;IACpE,mEAAmE;IACnE,MAAM,CAAC,SAAS,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC9D"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=remote-executor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"remote-executor.js","sourceRoot":"","sources":["../src/remote-executor.ts"],"names":[],"mappings":""}
@@ -0,0 +1,16 @@
1
+ import type { PowerLineConnection } from "./adapter.js";
2
+ import type { RemoteExecutor } from "./remote-executor.js";
3
+ import type { AdapterLogger } from "./logger.js";
4
+ /**
5
+ * Stop the remote PowerLine process and close the tunnel.
6
+ * Shared by SSH and Codespace adapters.
7
+ */
8
+ export declare function remoteStop(environmentId: string, executor: RemoteExecutor, logger?: AdapterLogger): Promise<void>;
9
+ /**
10
+ * Stop the remote PowerLine, remove artifacts, and close the tunnel.
11
+ * Shared by SSH and Codespace adapters.
12
+ */
13
+ export declare function remoteDestroy(environmentId: string, executor: RemoteExecutor, logger?: AdapterLogger): Promise<void>;
14
+ /** Check that the tunnel is alive and the PowerLine responds to a ping. */
15
+ export declare function remoteHealthCheck(connection: PowerLineConnection): Promise<boolean>;
16
+ //# sourceMappingURL=shared-operations.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-operations.d.ts","sourceRoot":"","sources":["../src/shared-operations.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AACxD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAI3D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD;;;GAGG;AACH,wBAAsB,UAAU,CAC9B,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,cAAc,EACxB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CAOf;AAED;;;GAGG;AACH,wBAAsB,aAAa,CACjC,aAAa,EAAE,MAAM,EACrB,QAAQ,EAAE,cAAc,EACxB,MAAM,GAAE,aAA6B,GACpC,OAAO,CAAC,IAAI,CAAC,CAcf;AAED,2EAA2E;AAC3E,wBAAsB,iBAAiB,CAAC,UAAU,EAAE,mBAAmB,GAAG,OAAO,CAAC,OAAO,CAAC,CAWzF"}
@@ -0,0 +1,50 @@
1
+ import { closeTunnel, getTunnel } from "./tunnel-registry.js";
2
+ import { buildRemoteKillCommand } from "./bootstrap.js";
3
+ import { REMOTE_POWERLINE_DIRECTORY } from "./utils.js";
4
+ import { defaultLogger } from "./logger.js";
5
+ /**
6
+ * Stop the remote PowerLine process and close the tunnel.
7
+ * Shared by SSH and Codespace adapters.
8
+ */
9
+ export async function remoteStop(environmentId, executor, logger = defaultLogger) {
10
+ try {
11
+ await executor.exec(buildRemoteKillCommand());
12
+ }
13
+ catch (err) {
14
+ logger.debug({ environmentId, err }, "Failed to kill remote PowerLine (may already be stopped)");
15
+ }
16
+ await closeTunnel(environmentId);
17
+ }
18
+ /**
19
+ * Stop the remote PowerLine, remove artifacts, and close the tunnel.
20
+ * Shared by SSH and Codespace adapters.
21
+ */
22
+ export async function remoteDestroy(environmentId, executor, logger = defaultLogger) {
23
+ try {
24
+ await executor.exec(`${buildRemoteKillCommand()}; `
25
+ + 'CRED="$HOME/.claude/.credentials.json"; '
26
+ + `if [ -L "$CRED" ]; then case "$(readlink "$CRED" 2>/dev/null)" in ${REMOTE_POWERLINE_DIRECTORY}/*) rm -f "$CRED";; esac; fi; `
27
+ + `HELPER="$(git config --global credential.helper 2>/dev/null || true)"; `
28
+ + `case "$HELPER" in ${REMOTE_POWERLINE_DIRECTORY}/*) git config --global --unset credential.helper 2>/dev/null || true;; esac; `
29
+ + `rm -rf ${REMOTE_POWERLINE_DIRECTORY}`);
30
+ }
31
+ catch (err) {
32
+ logger.debug({ environmentId, err }, "Failed to clean up remote PowerLine artifacts");
33
+ }
34
+ await closeTunnel(environmentId);
35
+ }
36
+ /** Check that the tunnel is alive and the PowerLine responds to a ping. */
37
+ export async function remoteHealthCheck(connection) {
38
+ const state = getTunnel(connection.environmentId);
39
+ if (!state?.tunnel.isAlive()) {
40
+ return false;
41
+ }
42
+ try {
43
+ await connection.client.ping({});
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
50
+ //# sourceMappingURL=shared-operations.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"shared-operations.js","sourceRoot":"","sources":["../src/shared-operations.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,sBAAsB,CAAC;AAC9D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,OAAO,EAAE,0BAA0B,EAAE,MAAM,YAAY,CAAC;AAExD,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAE5C;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAC9B,aAAqB,EACrB,QAAwB,EACxB,SAAwB,aAAa;IAErC,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,IAAI,CAAC,sBAAsB,EAAE,CAAC,CAAC;IAChD,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,GAAG,EAAE,EAAE,0DAA0D,CAAC,CAAC;IACnG,CAAC;IACD,MAAM,WAAW,CAAC,aAAa,CAAC,CAAC;AACnC,CAAC;AAED;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,aAAqB,EACrB,QAAwB,EACxB,SAAwB,aAAa;IAErC,IAAI,CAAC;QACH,MAAM,QAAQ,CAAC,IAAI,CACjB,GAAG,sBAAsB,EAAE,IAAI;cAC7B,0CAA0C;cAC1C,qEAAqE,0BAA0B,gCAAgC;cAC/H,yEAAyE;cACzE,qBAAqB,0BAA0B,gFAAgF;cAC/H,UAAU,0BAA0B,EAAE,CACzC,CAAC;IACJ,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,GAAG,EAAE,EAAE,+CAA+C,CAAC,CAAC;IACxF,CAAC;IACD,MAAM,WAAW,CAAC,aAAa,CAAC,CAAC;AACnC,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,KAAK,UAAU,iBAAiB,CAAC,UAA+B;IACrE,MAAM,KAAK,GAAG,SAAS,CAAC,UAAU,CAAC,aAAa,CAAC,CAAC;IAClD,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,OAAO,EAAE,EAAE,CAAC;QAC7B,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,CAAC;QACH,MAAM,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACjC,OAAO,IAAI,CAAC;IACd,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAC;IACf,CAAC;AACH,CAAC"}
@@ -0,0 +1,17 @@
1
+ import type { RemoteTunnel } from "./tunnel.js";
2
+ import type { AdapterLogger } from "./logger.js";
3
+ /** State for an active tunnel pair (forward + optional reverse). */
4
+ export interface TunnelState {
5
+ tunnel: RemoteTunnel;
6
+ /** Optional reverse tunnel so remote agents can reach the host MCP endpoint. */
7
+ reverseTunnel?: RemoteTunnel;
8
+ }
9
+ /** Register an active tunnel for an environment, closing any existing tunnel first. */
10
+ export declare function registerTunnel(environmentId: string, state: TunnelState, logger?: AdapterLogger): void;
11
+ /** Get the tunnel state for an environment. */
12
+ export declare function getTunnel(environmentId: string): TunnelState | undefined;
13
+ /** Close and unregister the tunnel(s) for an environment. */
14
+ export declare function closeTunnel(environmentId: string): Promise<void>;
15
+ /** Close all active tunnels (called during server shutdown). */
16
+ export declare function closeAllTunnels(logger?: AdapterLogger): Promise<void>;
17
+ //# sourceMappingURL=tunnel-registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-registry.d.ts","sourceRoot":"","sources":["../src/tunnel-registry.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAGjD,oEAAoE;AACpE,MAAM,WAAW,WAAW;IAC1B,MAAM,EAAE,YAAY,CAAC;IACrB,gFAAgF;IAChF,aAAa,CAAC,EAAE,YAAY,CAAC;CAC9B;AAID,uFAAuF;AACvF,wBAAgB,cAAc,CAC5B,aAAa,EAAE,MAAM,EACrB,KAAK,EAAE,WAAW,EAClB,MAAM,GAAE,aAA6B,GACpC,IAAI,CAaN;AAED,+CAA+C;AAC/C,wBAAgB,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS,CAExE;AAED,6DAA6D;AAC7D,wBAAsB,WAAW,CAAC,aAAa,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAStE;AAED,gEAAgE;AAChE,wBAAsB,eAAe,CAAC,MAAM,GAAE,aAA6B,GAAG,OAAO,CAAC,IAAI,CAAC,CAS1F"}
@@ -0,0 +1,45 @@
1
+ import { defaultLogger } from "./logger.js";
2
+ const tunnelMap = new Map();
3
+ /** Register an active tunnel for an environment, closing any existing tunnel first. */
4
+ export function registerTunnel(environmentId, state, logger = defaultLogger) {
5
+ const existing = tunnelMap.get(environmentId);
6
+ if (existing) {
7
+ existing.tunnel.close().catch((err) => {
8
+ logger.warn({ err, environmentId }, "Failed to close existing tunnel before registering new one");
9
+ });
10
+ if (existing.reverseTunnel) {
11
+ existing.reverseTunnel.close().catch((err) => {
12
+ logger.warn({ err, environmentId }, "Failed to close existing reverse tunnel before registering new one");
13
+ });
14
+ }
15
+ }
16
+ tunnelMap.set(environmentId, state);
17
+ }
18
+ /** Get the tunnel state for an environment. */
19
+ export function getTunnel(environmentId) {
20
+ return tunnelMap.get(environmentId);
21
+ }
22
+ /** Close and unregister the tunnel(s) for an environment. */
23
+ export async function closeTunnel(environmentId) {
24
+ const state = tunnelMap.get(environmentId);
25
+ if (state) {
26
+ await state.tunnel.close();
27
+ if (state.reverseTunnel) {
28
+ await state.reverseTunnel.close();
29
+ }
30
+ tunnelMap.delete(environmentId);
31
+ }
32
+ }
33
+ /** Close all active tunnels (called during server shutdown). */
34
+ export async function closeAllTunnels(logger = defaultLogger) {
35
+ const ids = [...tunnelMap.keys()];
36
+ for (const id of ids) {
37
+ try {
38
+ await closeTunnel(id);
39
+ }
40
+ catch (err) {
41
+ logger.error({ environmentId: id, err }, "Failed to close tunnel during shutdown");
42
+ }
43
+ }
44
+ }
45
+ //# sourceMappingURL=tunnel-registry.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel-registry.js","sourceRoot":"","sources":["../src/tunnel-registry.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAS5C,MAAM,SAAS,GAA6B,IAAI,GAAG,EAAuB,CAAC;AAE3E,uFAAuF;AACvF,MAAM,UAAU,cAAc,CAC5B,aAAqB,EACrB,KAAkB,EAClB,SAAwB,aAAa;IAErC,MAAM,QAAQ,GAAG,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC9C,IAAI,QAAQ,EAAE,CAAC;QACb,QAAQ,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;YACpC,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,4DAA4D,CAAC,CAAC;QACpG,CAAC,CAAC,CAAC;QACH,IAAI,QAAQ,CAAC,aAAa,EAAE,CAAC;YAC3B,QAAQ,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;gBAC3C,MAAM,CAAC,IAAI,CAAC,EAAE,GAAG,EAAE,aAAa,EAAE,EAAE,oEAAoE,CAAC,CAAC;YAC5G,CAAC,CAAC,CAAC;QACL,CAAC;IACH,CAAC;IACD,SAAS,CAAC,GAAG,CAAC,aAAa,EAAE,KAAK,CAAC,CAAC;AACtC,CAAC;AAED,+CAA+C;AAC/C,MAAM,UAAU,SAAS,CAAC,aAAqB;IAC7C,OAAO,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;AACtC,CAAC;AAED,6DAA6D;AAC7D,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,aAAqB;IACrD,MAAM,KAAK,GAAG,SAAS,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC;IAC3C,IAAI,KAAK,EAAE,CAAC;QACV,MAAM,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC;QAC3B,IAAI,KAAK,CAAC,aAAa,EAAE,CAAC;YACxB,MAAM,KAAK,CAAC,aAAa,CAAC,KAAK,EAAE,CAAC;QACpC,CAAC;QACD,SAAS,CAAC,MAAM,CAAC,aAAa,CAAC,CAAC;IAClC,CAAC;AACH,CAAC;AAED,gEAAgE;AAChE,MAAM,CAAC,KAAK,UAAU,eAAe,CAAC,SAAwB,aAAa;IACzE,MAAM,GAAG,GAAG,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,CAAC,CAAC;IAClC,KAAK,MAAM,EAAE,IAAI,GAAG,EAAE,CAAC;QACrB,IAAI,CAAC;YACH,MAAM,WAAW,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,CAAC,KAAK,CAAC,EAAE,aAAa,EAAE,EAAE,EAAE,GAAG,EAAE,EAAE,wCAAwC,CAAC,CAAC;QACrF,CAAC;IACH,CAAC;AACH,CAAC"}
@@ -0,0 +1,35 @@
1
+ import { type ChildProcess } from "node:child_process";
2
+ import type { AdapterLogger } from "./logger.js";
3
+ /** Abstraction for a long-lived port-forwarding tunnel. */
4
+ export interface RemoteTunnel {
5
+ /** The local port the tunnel is bound to. */
6
+ localPort: number;
7
+ /** Open the tunnel (spawns a background process). */
8
+ open(): Promise<void>;
9
+ /** Close the tunnel (kills the background process). */
10
+ close(): Promise<void>;
11
+ /** Return true if the tunnel process is still running. */
12
+ isAlive(): boolean;
13
+ }
14
+ /**
15
+ * Base class for tunnels backed by a long-lived child process.
16
+ * Subclasses provide the command and arguments to spawn.
17
+ */
18
+ export declare abstract class ProcessTunnel implements RemoteTunnel {
19
+ localPort: number;
20
+ protected process: ChildProcess | undefined;
21
+ protected logger: AdapterLogger;
22
+ constructor(localPort: number, logger?: AdapterLogger);
23
+ /** Return the command and arguments to spawn the tunnel process. */
24
+ protected abstract spawnArgs(): {
25
+ command: string;
26
+ args: string[];
27
+ };
28
+ /** Open the tunnel by spawning the background process. */
29
+ open(): Promise<void>;
30
+ /** Close the tunnel by killing the background process. */
31
+ close(): Promise<void>;
32
+ /** Return true if the tunnel process is still running. */
33
+ isAlive(): boolean;
34
+ }
35
+ //# sourceMappingURL=tunnel.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel.d.ts","sourceRoot":"","sources":["../src/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAS,KAAK,YAAY,EAAE,MAAM,oBAAoB,CAAC;AAC9D,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAQjD,2DAA2D;AAC3D,MAAM,WAAW,YAAY;IAC3B,6CAA6C;IAC7C,SAAS,EAAE,MAAM,CAAC;IAClB,qDAAqD;IACrD,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACtB,uDAAuD;IACvD,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;IACvB,0DAA0D;IAC1D,OAAO,IAAI,OAAO,CAAC;CACpB;AAED;;;GAGG;AACH,8BAAsB,aAAc,YAAW,YAAY;IAClD,SAAS,EAAE,MAAM,CAAC;IACzB,SAAS,CAAC,OAAO,EAAE,YAAY,GAAG,SAAS,CAAC;IAC5C,SAAS,CAAC,MAAM,EAAE,aAAa,CAAC;gBAEb,SAAS,EAAE,MAAM,EAAE,MAAM,GAAE,aAA6B;IAK3E,oEAAoE;IACpE,SAAS,CAAC,QAAQ,CAAC,SAAS,IAAI;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,EAAE,CAAA;KAAE;IAEnE,0DAA0D;IAC7C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;IA0BlC,0DAA0D;IAC7C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;IAanC,0DAA0D;IACnD,OAAO,IAAI,OAAO;CAG1B"}
package/dist/tunnel.js ADDED
@@ -0,0 +1,60 @@
1
+ import { spawn } from "node:child_process";
2
+ import { defaultLogger } from "./logger.js";
3
+ import { sleep } from "./utils.js";
4
+ import { waitForLocalPort } from "./connect.js";
5
+ /** Grace period before sending SIGKILL to a tunnel process. */
6
+ const TUNNEL_KILL_GRACE_MS = 1_000;
7
+ /**
8
+ * Base class for tunnels backed by a long-lived child process.
9
+ * Subclasses provide the command and arguments to spawn.
10
+ */
11
+ export class ProcessTunnel {
12
+ localPort;
13
+ process;
14
+ logger;
15
+ constructor(localPort, logger = defaultLogger) {
16
+ this.localPort = localPort;
17
+ this.logger = logger;
18
+ }
19
+ /** Open the tunnel by spawning the background process. */
20
+ async open() {
21
+ const { command, args } = this.spawnArgs();
22
+ this.logger.info({ command, args }, "Opening tunnel");
23
+ this.process = spawn(command, args, {
24
+ stdio: ["ignore", "ignore", "pipe"],
25
+ detached: false,
26
+ });
27
+ this.process.on("error", (err) => {
28
+ this.logger.error({ err }, "Tunnel process error");
29
+ });
30
+ this.process.stderr?.on("data", (data) => {
31
+ this.logger.debug({ stderr: data.toString() }, "Tunnel stderr");
32
+ });
33
+ // Wait for the local port to become reachable. Kill the process if it fails.
34
+ try {
35
+ await waitForLocalPort(this.localPort);
36
+ }
37
+ catch (err) {
38
+ await this.close();
39
+ throw err;
40
+ }
41
+ }
42
+ /** Close the tunnel by killing the background process. */
43
+ async close() {
44
+ if (this.process?.exitCode !== null) {
45
+ return;
46
+ }
47
+ this.process.kill("SIGTERM");
48
+ await sleep(TUNNEL_KILL_GRACE_MS);
49
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- exitCode may change after SIGTERM + sleep
50
+ if (this.process.exitCode === null) {
51
+ this.process.kill("SIGKILL");
52
+ }
53
+ this.process = undefined;
54
+ }
55
+ /** Return true if the tunnel process is still running. */
56
+ isAlive() {
57
+ return this.process?.exitCode === null;
58
+ }
59
+ }
60
+ //# sourceMappingURL=tunnel.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tunnel.js","sourceRoot":"","sources":["../src/tunnel.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,KAAK,EAAqB,MAAM,oBAAoB,CAAC;AAE9D,OAAO,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAC5C,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,gBAAgB,EAAE,MAAM,cAAc,CAAC;AAEhD,+DAA+D;AAC/D,MAAM,oBAAoB,GAAW,KAAK,CAAC;AAc3C;;;GAGG;AACH,MAAM,OAAgB,aAAa;IAC1B,SAAS,CAAS;IACf,OAAO,CAA2B;IAClC,MAAM,CAAgB;IAEhC,YAAmB,SAAiB,EAAE,SAAwB,aAAa;QACzE,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAC3B,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC;IACvB,CAAC;IAKD,0DAA0D;IACnD,KAAK,CAAC,IAAI;QACf,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,EAAE,CAAC;QAC3C,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,EAAE,gBAAgB,CAAC,CAAC;QAEtD,IAAI,CAAC,OAAO,GAAG,KAAK,CAAC,OAAO,EAAE,IAAI,EAAE;YAClC,KAAK,EAAE,CAAC,QAAQ,EAAE,QAAQ,EAAE,MAAM,CAAC;YACnC,QAAQ,EAAE,KAAK;SAChB,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,EAAE;YAC/B,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,GAAG,EAAE,EAAE,sBAAsB,CAAC,CAAC;QACrD,CAAC,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,EAAE,CAAC,IAAY,EAAE,EAAE;YAC/C,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,QAAQ,EAAE,EAAE,EAAE,eAAe,CAAC,CAAC;QAClE,CAAC,CAAC,CAAC;QAEH,6EAA6E;QAC7E,IAAI,CAAC;YACH,MAAM,gBAAgB,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QACzC,CAAC;QAAC,OAAO,GAAG,EAAE,CAAC;YACb,MAAM,IAAI,CAAC,KAAK,EAAE,CAAC;YACnB,MAAM,GAAG,CAAC;QACZ,CAAC;IACH,CAAC;IAED,0DAA0D;IACnD,KAAK,CAAC,KAAK;QAChB,IAAI,IAAI,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,EAAE,CAAC;YACpC,OAAO;QACT,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC7B,MAAM,KAAK,CAAC,oBAAoB,CAAC,CAAC;QAClC,oHAAoH;QACpH,IAAI,IAAI,CAAC,OAAO,CAAC,QAAQ,KAAK,IAAI,EAAE,CAAC;YACnC,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;QAC/B,CAAC;QACD,IAAI,CAAC,OAAO,GAAG,SAAS,CAAC;IAC3B,CAAC;IAED,0DAA0D;IACnD,OAAO;QACZ,OAAO,IAAI,CAAC,OAAO,EAAE,QAAQ,KAAK,IAAI,CAAC;IACzC,CAAC;CACF"}
@@ -0,0 +1,27 @@
1
+ /** Remote directory where PowerLine artifacts are installed. Uses $HOME (not ~) so it expands inside double-quoted shell strings. */
2
+ export declare const REMOTE_POWERLINE_DIRECTORY: string;
3
+ /** Timeout for the initial SSH connectivity test. */
4
+ export declare const SSH_CONNECTIVITY_TIMEOUT_MS: number;
5
+ /** Default timeout for remote command execution. */
6
+ export declare const REMOTE_EXEC_DEFAULT_TIMEOUT_MS: number;
7
+ /** Return a promise that resolves after the specified number of milliseconds. */
8
+ export declare function sleep(ms: number): Promise<void>;
9
+ /** Find and return an available TCP port by briefly binding to port 0. */
10
+ export declare function findFreePort(): Promise<number>;
11
+ /**
12
+ * Check if we are running from a monorepo source checkout.
13
+ * We detect this by checking for `rush.json` at the repo root,
14
+ * computed relative to this file's compiled location (packages/adapter-sdk/dist → 3 levels up).
15
+ */
16
+ export declare function isDevMode(): boolean;
17
+ /**
18
+ * Read the lockstep version from the SDK's own package.json.
19
+ * import.meta.dirname = dist/, so ../package.json = adapter-sdk's package.json.
20
+ */
21
+ export declare function getPackageVersion(): string;
22
+ /**
23
+ * Escape a value for safe use inside a shell single-quoted string.
24
+ * Replaces each `'` with `'\''` (end quote, escaped quote, start quote).
25
+ */
26
+ export declare function shellEscape(value: string): string;
27
+ //# sourceMappingURL=utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.d.ts","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAMA,qIAAqI;AACrI,eAAO,MAAM,0BAA0B,EAAE,MAAmC,CAAC;AAE7E,qDAAqD;AACrD,eAAO,MAAM,2BAA2B,EAAE,MAAe,CAAC;AAE1D,oDAAoD;AACpD,eAAO,MAAM,8BAA8B,EAAE,MAAe,CAAC;AAI7D,iFAAiF;AACjF,wBAAgB,KAAK,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAE/C;AAED,0EAA0E;AAC1E,wBAAgB,YAAY,IAAI,OAAO,CAAC,MAAM,CAAC,CAc9C;AAED;;;;GAIG;AACH,wBAAgB,SAAS,IAAI,OAAO,CAGnC;AAED;;;GAGG;AACH,wBAAgB,iBAAiB,IAAI,MAAM,CAI1C;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAEjD"}
package/dist/utils.js ADDED
@@ -0,0 +1,58 @@
1
+ import { createServer } from "node:net";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { join, resolve } from "node:path";
4
+ // ─── Constants ──────────────────────────────────────────────
5
+ /** Remote directory where PowerLine artifacts are installed. Uses $HOME (not ~) so it expands inside double-quoted shell strings. */
6
+ export const REMOTE_POWERLINE_DIRECTORY = "$HOME/.grackle/powerline";
7
+ /** Timeout for the initial SSH connectivity test. */
8
+ export const SSH_CONNECTIVITY_TIMEOUT_MS = 15_000;
9
+ /** Default timeout for remote command execution. */
10
+ export const REMOTE_EXEC_DEFAULT_TIMEOUT_MS = 60_000;
11
+ // ─── Utilities ──────────────────────────────────────────────
12
+ /** Return a promise that resolves after the specified number of milliseconds. */
13
+ export function sleep(ms) {
14
+ return new Promise((resolve) => setTimeout(resolve, ms));
15
+ }
16
+ /** Find and return an available TCP port by briefly binding to port 0. */
17
+ export function findFreePort() {
18
+ return new Promise((resolve, reject) => {
19
+ const server = createServer();
20
+ server.listen(0, () => {
21
+ const addr = server.address();
22
+ if (addr && typeof addr === "object") {
23
+ const port = addr.port;
24
+ server.close(() => resolve(port));
25
+ }
26
+ else {
27
+ server.close(() => reject(new Error("Failed to get port")));
28
+ }
29
+ });
30
+ server.on("error", reject);
31
+ });
32
+ }
33
+ /**
34
+ * Check if we are running from a monorepo source checkout.
35
+ * We detect this by checking for `rush.json` at the repo root,
36
+ * computed relative to this file's compiled location (packages/adapter-sdk/dist → 3 levels up).
37
+ */
38
+ export function isDevMode() {
39
+ const repoRoot = resolve(import.meta.dirname, "../../../");
40
+ return existsSync(join(repoRoot, "rush.json"));
41
+ }
42
+ /**
43
+ * Read the lockstep version from the SDK's own package.json.
44
+ * import.meta.dirname = dist/, so ../package.json = adapter-sdk's package.json.
45
+ */
46
+ export function getPackageVersion() {
47
+ const packageJsonPath = resolve(import.meta.dirname, "../package.json");
48
+ const pkg = JSON.parse(readFileSync(packageJsonPath, "utf8"));
49
+ return pkg.version;
50
+ }
51
+ /**
52
+ * Escape a value for safe use inside a shell single-quoted string.
53
+ * Replaces each `'` with `'\''` (end quote, escaped quote, start quote).
54
+ */
55
+ export function shellEscape(value) {
56
+ return value.replace(/'/g, "'\\''");
57
+ }
58
+ //# sourceMappingURL=utils.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"utils.js","sourceRoot":"","sources":["../src/utils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AACxC,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACnD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAE1C,+DAA+D;AAE/D,qIAAqI;AACrI,MAAM,CAAC,MAAM,0BAA0B,GAAW,0BAA0B,CAAC;AAE7E,qDAAqD;AACrD,MAAM,CAAC,MAAM,2BAA2B,GAAW,MAAM,CAAC;AAE1D,oDAAoD;AACpD,MAAM,CAAC,MAAM,8BAA8B,GAAW,MAAM,CAAC;AAE7D,+DAA+D;AAE/D,iFAAiF;AACjF,MAAM,UAAU,KAAK,CAAC,EAAU;IAC9B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAC;AAC3D,CAAC;AAED,0EAA0E;AAC1E,MAAM,UAAU,YAAY;IAC1B,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;QACrC,MAAM,MAAM,GAAG,YAAY,EAAE,CAAC;QAC9B,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,GAAG,EAAE;YACpB,MAAM,IAAI,GAAG,MAAM,CAAC,OAAO,EAAE,CAAC;YAC9B,IAAI,IAAI,IAAI,OAAO,IAAI,KAAK,QAAQ,EAAE,CAAC;gBACrC,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;gBACvB,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;YACpC,CAAC;iBAAM,CAAC;gBACN,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAC7B,CAAC,CAAC,CAAC;AACL,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,SAAS;IACvB,MAAM,QAAQ,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,WAAW,CAAC,CAAC;IAC3D,OAAO,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,WAAW,CAAC,CAAC,CAAC;AACjD,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,iBAAiB;IAC/B,MAAM,eAAe,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,iBAAiB,CAAC,CAAC;IACxE,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,eAAe,EAAE,MAAM,CAAC,CAAwB,CAAC;IACrF,OAAO,GAAG,CAAC,OAAO,CAAC;AACrB,CAAC;AAED;;;GAGG;AACH,MAAM,UAAU,WAAW,CAAC,KAAa;IACvC,OAAO,KAAK,CAAC,OAAO,CAAC,IAAI,EAAE,OAAO,CAAC,CAAC;AACtC,CAAC"}
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@grackle-ai/adapter-sdk",
3
+ "version": "0.36.0",
4
+ "description": "SDK for building Grackle environment adapters",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/nick-pape/grackle.git",
9
+ "directory": "packages/adapter-sdk"
10
+ },
11
+ "keywords": [
12
+ "grackle",
13
+ "adapter",
14
+ "sdk",
15
+ "environment"
16
+ ],
17
+ "engines": {
18
+ "node": ">=22.0.0"
19
+ },
20
+ "type": "module",
21
+ "main": "dist/index.js",
22
+ "types": "dist/index.d.ts",
23
+ "files": [
24
+ "dist/"
25
+ ],
26
+ "dependencies": {
27
+ "@bufbuild/protobuf": "^2.5.0",
28
+ "@connectrpc/connect": "^2.0.0",
29
+ "@connectrpc/connect-node": "^2.0.0",
30
+ "@grackle-ai/common": "0.36.0"
31
+ },
32
+ "devDependencies": {
33
+ "@rushstack/heft": "1.2.4",
34
+ "@types/node": "^22.0.0",
35
+ "vitest": "^3.1.1",
36
+ "@grackle-ai/heft-rig": "0.0.1"
37
+ },
38
+ "scripts": {
39
+ "build": "heft build --clean",
40
+ "test": "vitest run",
41
+ "clean": "heft clean"
42
+ }
43
+ }