@webmux/agent 0.2.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-EWE7ZUYJ.js +163 -0
- package/dist/chunk-INUNCXBM.js +202 -0
- package/dist/cli.js +182 -1216
- package/dist/connection-RJY775NL.js +1046 -0
- package/dist/tmux-QIB4H3UA.js +15 -0
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
AGENT_PACKAGE_NAME,
|
|
4
|
+
AGENT_VERSION,
|
|
5
|
+
SERVICE_NAME,
|
|
6
|
+
installService,
|
|
7
|
+
readInstalledServiceConfig,
|
|
8
|
+
servicePath,
|
|
9
|
+
uninstallService,
|
|
10
|
+
upgradeService
|
|
11
|
+
} from "./chunk-INUNCXBM.js";
|
|
2
12
|
|
|
3
13
|
// src/cli.ts
|
|
4
|
-
import
|
|
5
|
-
import { execFileSync
|
|
14
|
+
import os2 from "os";
|
|
15
|
+
import { execFileSync } from "child_process";
|
|
16
|
+
import { pathToFileURL } from "url";
|
|
6
17
|
import { Command } from "commander";
|
|
7
18
|
|
|
8
19
|
// src/credentials.ts
|
|
@@ -41,1247 +52,202 @@ function saveCredentials(creds) {
|
|
|
41
52
|
});
|
|
42
53
|
}
|
|
43
54
|
|
|
44
|
-
// src/
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
};
|
|
66
|
-
}
|
|
67
|
-
function compareSemanticVersions(left, right) {
|
|
68
|
-
const parsedLeft = parseSemanticVersion(left);
|
|
69
|
-
const parsedRight = parseSemanticVersion(right);
|
|
70
|
-
if (!parsedLeft || !parsedRight) {
|
|
71
|
-
throw new Error(`Invalid semantic version comparison: "${left}" vs "${right}"`);
|
|
72
|
-
}
|
|
73
|
-
if (parsedLeft.major !== parsedRight.major) {
|
|
74
|
-
return parsedLeft.major - parsedRight.major;
|
|
75
|
-
}
|
|
76
|
-
if (parsedLeft.minor !== parsedRight.minor) {
|
|
77
|
-
return parsedLeft.minor - parsedRight.minor;
|
|
78
|
-
}
|
|
79
|
-
if (parsedLeft.patch !== parsedRight.patch) {
|
|
80
|
-
return parsedLeft.patch - parsedRight.patch;
|
|
81
|
-
}
|
|
82
|
-
return comparePrerelease(parsedLeft.prerelease, parsedRight.prerelease);
|
|
83
|
-
}
|
|
84
|
-
function comparePrerelease(left, right) {
|
|
85
|
-
if (left.length === 0 && right.length === 0) {
|
|
86
|
-
return 0;
|
|
87
|
-
}
|
|
88
|
-
if (left.length === 0) {
|
|
89
|
-
return 1;
|
|
90
|
-
}
|
|
91
|
-
if (right.length === 0) {
|
|
92
|
-
return -1;
|
|
93
|
-
}
|
|
94
|
-
const maxLength = Math.max(left.length, right.length);
|
|
95
|
-
for (let index = 0; index < maxLength; index += 1) {
|
|
96
|
-
const leftIdentifier = left[index];
|
|
97
|
-
const rightIdentifier = right[index];
|
|
98
|
-
if (leftIdentifier === void 0) {
|
|
99
|
-
return -1;
|
|
100
|
-
}
|
|
101
|
-
if (rightIdentifier === void 0) {
|
|
102
|
-
return 1;
|
|
103
|
-
}
|
|
104
|
-
const numericLeft = Number.parseInt(leftIdentifier, 10);
|
|
105
|
-
const numericRight = Number.parseInt(rightIdentifier, 10);
|
|
106
|
-
const leftIsNumber = String(numericLeft) === leftIdentifier;
|
|
107
|
-
const rightIsNumber = String(numericRight) === rightIdentifier;
|
|
108
|
-
if (leftIsNumber && rightIsNumber && numericLeft !== numericRight) {
|
|
109
|
-
return numericLeft - numericRight;
|
|
110
|
-
}
|
|
111
|
-
if (leftIsNumber !== rightIsNumber) {
|
|
112
|
-
return leftIsNumber ? -1 : 1;
|
|
113
|
-
}
|
|
114
|
-
if (leftIdentifier !== rightIdentifier) {
|
|
115
|
-
return leftIdentifier < rightIdentifier ? -1 : 1;
|
|
116
|
-
}
|
|
117
|
-
}
|
|
118
|
-
return 0;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// src/service.ts
|
|
122
|
-
import fs2 from "fs";
|
|
123
|
-
import os2 from "os";
|
|
124
|
-
import path2 from "path";
|
|
125
|
-
import { execFileSync } from "child_process";
|
|
126
|
-
var SERVICE_NAME = "webmux-agent";
|
|
127
|
-
function renderServiceUnit(options) {
|
|
128
|
-
return `[Unit]
|
|
129
|
-
Description=Webmux Agent (${options.agentName})
|
|
130
|
-
After=network-online.target
|
|
131
|
-
Wants=network-online.target
|
|
132
|
-
|
|
133
|
-
[Service]
|
|
134
|
-
Type=simple
|
|
135
|
-
ExecStart=${options.nodePath} ${options.cliPath} start
|
|
136
|
-
Restart=always
|
|
137
|
-
RestartSec=10
|
|
138
|
-
Environment=WEBMUX_AGENT_SERVICE=1
|
|
139
|
-
Environment=WEBMUX_AGENT_AUTO_UPGRADE=${options.autoUpgrade ? "1" : "0"}
|
|
140
|
-
Environment=WEBMUX_AGENT_NAME=${options.agentName}
|
|
141
|
-
Environment=HOME=${options.homeDir}
|
|
142
|
-
Environment=PATH=${options.pathEnv}
|
|
143
|
-
WorkingDirectory=${options.homeDir}
|
|
144
|
-
|
|
145
|
-
[Install]
|
|
146
|
-
WantedBy=default.target
|
|
147
|
-
`;
|
|
148
|
-
}
|
|
149
|
-
function installService(options) {
|
|
150
|
-
const homeDir = options.homeDir ?? os2.homedir();
|
|
151
|
-
const autoUpgrade = options.autoUpgrade;
|
|
152
|
-
const release = installManagedRelease({
|
|
153
|
-
packageName: options.packageName,
|
|
154
|
-
version: options.version,
|
|
155
|
-
homeDir
|
|
156
|
-
});
|
|
157
|
-
writeServiceUnit({
|
|
158
|
-
agentName: options.agentName,
|
|
159
|
-
autoUpgrade,
|
|
160
|
-
cliPath: release.cliPath,
|
|
161
|
-
homeDir
|
|
162
|
-
});
|
|
163
|
-
runSystemctl(["--user", "daemon-reload"]);
|
|
164
|
-
runSystemctl(["--user", "enable", SERVICE_NAME]);
|
|
165
|
-
runSystemctl(["--user", "restart", SERVICE_NAME]);
|
|
166
|
-
runCommand("loginctl", ["enable-linger", os2.userInfo().username]);
|
|
167
|
-
}
|
|
168
|
-
function upgradeService(options) {
|
|
169
|
-
const homeDir = options.homeDir ?? os2.homedir();
|
|
170
|
-
const installedConfig = readInstalledServiceConfig(homeDir);
|
|
171
|
-
const autoUpgrade = options.autoUpgrade ?? installedConfig?.autoUpgrade ?? true;
|
|
172
|
-
const release = installManagedRelease({
|
|
173
|
-
packageName: options.packageName,
|
|
174
|
-
version: options.version,
|
|
175
|
-
homeDir
|
|
176
|
-
});
|
|
177
|
-
writeServiceUnit({
|
|
178
|
-
agentName: options.agentName,
|
|
179
|
-
autoUpgrade,
|
|
180
|
-
cliPath: release.cliPath,
|
|
181
|
-
homeDir
|
|
182
|
-
});
|
|
183
|
-
runSystemctl(["--user", "daemon-reload"]);
|
|
184
|
-
runSystemctl(["--user", "restart", SERVICE_NAME]);
|
|
185
|
-
}
|
|
186
|
-
function uninstallService(homeDir = os2.homedir()) {
|
|
187
|
-
const unitPath = servicePath(homeDir);
|
|
188
|
-
try {
|
|
189
|
-
runSystemctl(["--user", "stop", SERVICE_NAME]);
|
|
190
|
-
} catch {
|
|
191
|
-
}
|
|
192
|
-
try {
|
|
193
|
-
runSystemctl(["--user", "disable", SERVICE_NAME]);
|
|
194
|
-
} catch {
|
|
195
|
-
}
|
|
196
|
-
if (fs2.existsSync(unitPath)) {
|
|
197
|
-
fs2.unlinkSync(unitPath);
|
|
198
|
-
}
|
|
199
|
-
try {
|
|
200
|
-
runSystemctl(["--user", "daemon-reload"]);
|
|
201
|
-
} catch {
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function readInstalledServiceConfig(homeDir = os2.homedir()) {
|
|
205
|
-
const unitPath = servicePath(homeDir);
|
|
206
|
-
if (!fs2.existsSync(unitPath)) {
|
|
207
|
-
return null;
|
|
208
|
-
}
|
|
209
|
-
const unit = fs2.readFileSync(unitPath, "utf-8");
|
|
210
|
-
const autoUpgradeMatch = unit.match(/^Environment=WEBMUX_AGENT_AUTO_UPGRADE=(\d)$/m);
|
|
211
|
-
const versionMatch = unit.match(/\/releases\/([^/\s]+)\/node_modules\//);
|
|
212
|
-
return {
|
|
213
|
-
autoUpgrade: autoUpgradeMatch?.[1] !== "0",
|
|
214
|
-
version: versionMatch?.[1] ?? null
|
|
215
|
-
};
|
|
216
|
-
}
|
|
217
|
-
function servicePath(homeDir = os2.homedir()) {
|
|
218
|
-
return path2.join(homeDir, ".config", "systemd", "user", `${SERVICE_NAME}.service`);
|
|
219
|
-
}
|
|
220
|
-
function writeServiceUnit(options) {
|
|
221
|
-
const serviceDir = path2.dirname(servicePath(options.homeDir));
|
|
222
|
-
fs2.mkdirSync(serviceDir, { recursive: true });
|
|
223
|
-
fs2.writeFileSync(
|
|
224
|
-
servicePath(options.homeDir),
|
|
225
|
-
renderServiceUnit({
|
|
226
|
-
agentName: options.agentName,
|
|
227
|
-
autoUpgrade: options.autoUpgrade,
|
|
228
|
-
cliPath: options.cliPath,
|
|
229
|
-
homeDir: options.homeDir,
|
|
230
|
-
nodePath: findBinary("node") ?? process.execPath,
|
|
231
|
-
pathEnv: process.env.PATH ?? ""
|
|
232
|
-
})
|
|
233
|
-
);
|
|
234
|
-
}
|
|
235
|
-
function installManagedRelease(options) {
|
|
236
|
-
const releaseDir = path2.join(options.homeDir, ".webmux", "releases", options.version);
|
|
237
|
-
const cliPath = path2.join(
|
|
238
|
-
releaseDir,
|
|
239
|
-
"node_modules",
|
|
240
|
-
...options.packageName.split("/"),
|
|
241
|
-
"dist",
|
|
242
|
-
"cli.js"
|
|
243
|
-
);
|
|
244
|
-
if (fs2.existsSync(cliPath)) {
|
|
245
|
-
return { cliPath, releaseDir };
|
|
246
|
-
}
|
|
247
|
-
fs2.mkdirSync(releaseDir, { recursive: true });
|
|
248
|
-
ensureRuntimePackageJson(releaseDir);
|
|
249
|
-
const packageManager = findBinary("pnpm") ? "pnpm" : "npm";
|
|
250
|
-
if (packageManager === "pnpm") {
|
|
251
|
-
runCommand("pnpm", ["add", "--dir", releaseDir, `${options.packageName}@${options.version}`]);
|
|
252
|
-
} else {
|
|
253
|
-
if (!findBinary("npm")) {
|
|
254
|
-
throw new Error("Cannot find pnpm or npm. Install one package manager before installing the service.");
|
|
255
|
-
}
|
|
256
|
-
runCommand("npm", ["install", "--omit=dev", `${options.packageName}@${options.version}`], releaseDir);
|
|
257
|
-
}
|
|
258
|
-
if (!fs2.existsSync(cliPath)) {
|
|
259
|
-
throw new Error(`Managed release did not produce a CLI at ${cliPath}`);
|
|
260
|
-
}
|
|
261
|
-
return { cliPath, releaseDir };
|
|
262
|
-
}
|
|
263
|
-
function ensureRuntimePackageJson(releaseDir) {
|
|
264
|
-
const packageJsonPath = path2.join(releaseDir, "package.json");
|
|
265
|
-
if (fs2.existsSync(packageJsonPath)) {
|
|
266
|
-
return;
|
|
267
|
-
}
|
|
268
|
-
fs2.writeFileSync(
|
|
269
|
-
packageJsonPath,
|
|
270
|
-
JSON.stringify({
|
|
271
|
-
name: "webmux-agent-runtime",
|
|
272
|
-
private: true
|
|
273
|
-
}, null, 2) + "\n"
|
|
274
|
-
);
|
|
275
|
-
}
|
|
276
|
-
function runSystemctl(args) {
|
|
277
|
-
runCommand("systemctl", args);
|
|
278
|
-
}
|
|
279
|
-
function runCommand(command, args, cwd) {
|
|
280
|
-
execFileSync(command, args, {
|
|
281
|
-
cwd,
|
|
282
|
-
stdio: "inherit"
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
function findBinary(name) {
|
|
286
|
-
try {
|
|
287
|
-
return execFileSync("which", [name], { encoding: "utf-8" }).trim();
|
|
288
|
-
} catch {
|
|
289
|
-
return null;
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
// src/terminal.ts
|
|
294
|
-
import { spawn } from "node-pty";
|
|
295
|
-
|
|
296
|
-
// src/tmux.ts
|
|
297
|
-
import { execFile } from "child_process";
|
|
298
|
-
import { promisify } from "util";
|
|
299
|
-
var execFileAsync = promisify(execFile);
|
|
300
|
-
var FIELD_SEPARATOR = "";
|
|
301
|
-
var SESSION_NAME_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9._-]{0,31}$/;
|
|
302
|
-
var TMUX_EMPTY_STATE_MARKERS = [
|
|
303
|
-
"error connecting to",
|
|
304
|
-
"failed to connect to server",
|
|
305
|
-
"no server running",
|
|
306
|
-
"no sessions"
|
|
307
|
-
];
|
|
308
|
-
var TmuxClient = class {
|
|
309
|
-
socketName;
|
|
310
|
-
workspaceRoot;
|
|
311
|
-
constructor(options) {
|
|
312
|
-
this.socketName = options.socketName;
|
|
313
|
-
this.workspaceRoot = options.workspaceRoot;
|
|
314
|
-
}
|
|
315
|
-
async listSessions() {
|
|
316
|
-
const stdout = await this.run(
|
|
317
|
-
[
|
|
318
|
-
"list-sessions",
|
|
319
|
-
"-F",
|
|
320
|
-
[
|
|
321
|
-
"#{session_name}",
|
|
322
|
-
"#{session_windows}",
|
|
323
|
-
"#{session_attached}",
|
|
324
|
-
"#{session_created}",
|
|
325
|
-
"#{session_activity}",
|
|
326
|
-
"#{session_path}",
|
|
327
|
-
"#{pane_current_command}"
|
|
328
|
-
].join(FIELD_SEPARATOR)
|
|
329
|
-
],
|
|
330
|
-
{ allowEmptyState: true }
|
|
331
|
-
);
|
|
332
|
-
const sessions = parseSessionList(stdout);
|
|
333
|
-
const enriched = await Promise.all(
|
|
334
|
-
sessions.map(async (session) => ({
|
|
335
|
-
...session,
|
|
336
|
-
preview: await this.getPreview(session.name)
|
|
337
|
-
}))
|
|
338
|
-
);
|
|
339
|
-
return enriched.sort((left, right) => {
|
|
340
|
-
if (left.lastActivityAt !== right.lastActivityAt) {
|
|
341
|
-
return right.lastActivityAt - left.lastActivityAt;
|
|
342
|
-
}
|
|
343
|
-
return left.name.localeCompare(right.name);
|
|
344
|
-
});
|
|
345
|
-
}
|
|
346
|
-
async createSession(name) {
|
|
347
|
-
assertValidSessionName(name);
|
|
348
|
-
if (await this.hasSession(name)) {
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
await this.run(["new-session", "-d", "-s", name, "-c", this.workspaceRoot]);
|
|
352
|
-
}
|
|
353
|
-
async killSession(name) {
|
|
354
|
-
assertValidSessionName(name);
|
|
355
|
-
await this.run(["kill-session", "-t", name]);
|
|
356
|
-
}
|
|
357
|
-
async readSession(name) {
|
|
358
|
-
const sessions = await this.listSessions();
|
|
359
|
-
return sessions.find((session) => session.name === name) ?? null;
|
|
360
|
-
}
|
|
361
|
-
async hasSession(name) {
|
|
362
|
-
try {
|
|
363
|
-
await this.run(["has-session", "-t", name]);
|
|
364
|
-
return true;
|
|
365
|
-
} catch (error) {
|
|
366
|
-
const message = String(
|
|
367
|
-
error.stderr ?? error.message
|
|
368
|
-
);
|
|
369
|
-
if (isTmuxEmptyStateMessage(message)) {
|
|
370
|
-
return false;
|
|
371
|
-
}
|
|
372
|
-
return false;
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
async getPreview(name) {
|
|
376
|
-
try {
|
|
377
|
-
const stdout = await this.run(
|
|
378
|
-
["capture-pane", "-p", "-J", "-S", "-18", "-E", "-", "-t", `${name}:`],
|
|
379
|
-
{ allowEmptyState: true }
|
|
380
|
-
);
|
|
381
|
-
return formatPreview(stdout);
|
|
382
|
-
} catch {
|
|
383
|
-
return ["Session available. Tap to attach."];
|
|
384
|
-
}
|
|
385
|
-
}
|
|
386
|
-
async run(args, options = {}) {
|
|
55
|
+
// src/cli.ts
|
|
56
|
+
async function loadAgentRuntime() {
|
|
57
|
+
const [{ AgentConnection }, { TmuxClient }] = await Promise.all([
|
|
58
|
+
import("./connection-RJY775NL.js"),
|
|
59
|
+
import("./tmux-QIB4H3UA.js")
|
|
60
|
+
]);
|
|
61
|
+
return { AgentConnection, TmuxClient };
|
|
62
|
+
}
|
|
63
|
+
function createProgram() {
|
|
64
|
+
const program = new Command();
|
|
65
|
+
program.name("webmux-agent").description("Webmux agent \u2014 connects your machine to the webmux server").version(AGENT_VERSION);
|
|
66
|
+
program.command("register").description("Register this agent with a webmux server").requiredOption("--server <url>", "Server URL (e.g. https://webmux.example.com)").requiredOption("--token <token>", "One-time registration token from the server").option("--name <name>", "Display name for this agent (defaults to hostname)").action(async (opts) => {
|
|
67
|
+
const serverUrl = opts.server.replace(/\/+$/, "");
|
|
68
|
+
const agentName = opts.name ?? os2.hostname();
|
|
69
|
+
console.log(`[agent] Registering with server ${serverUrl}...`);
|
|
70
|
+
console.log(`[agent] Agent name: ${agentName}`);
|
|
71
|
+
const body = {
|
|
72
|
+
token: opts.token,
|
|
73
|
+
name: agentName
|
|
74
|
+
};
|
|
75
|
+
let response;
|
|
387
76
|
try {
|
|
388
|
-
|
|
389
|
-
"
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
cwd: this.workspaceRoot,
|
|
393
|
-
env: {
|
|
394
|
-
...process.env,
|
|
395
|
-
TERM: "xterm-256color"
|
|
396
|
-
}
|
|
397
|
-
}
|
|
398
|
-
);
|
|
399
|
-
return stdout;
|
|
400
|
-
} catch (error) {
|
|
401
|
-
const message = String(
|
|
402
|
-
error.stderr ?? error.message
|
|
403
|
-
);
|
|
404
|
-
if (options.allowEmptyState && TMUX_EMPTY_STATE_MARKERS.some((marker) => message.includes(marker))) {
|
|
405
|
-
return "";
|
|
406
|
-
}
|
|
407
|
-
throw error;
|
|
408
|
-
}
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
function assertValidSessionName(name) {
|
|
412
|
-
if (!SESSION_NAME_PATTERN.test(name)) {
|
|
413
|
-
throw new Error(
|
|
414
|
-
"Invalid session name. Use up to 32 letters, numbers, dot, dash, or underscore."
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
}
|
|
418
|
-
function parseSessionList(stdout) {
|
|
419
|
-
return stdout.split("\n").map((line) => line.trim()).filter(Boolean).flatMap((line) => {
|
|
420
|
-
const parts = line.split(FIELD_SEPARATOR);
|
|
421
|
-
const [name, windows, attachedClients, createdAt, lastActivityAt, path3] = parts;
|
|
422
|
-
const currentCommand = parts[6] ?? "";
|
|
423
|
-
if (!name || !windows || !attachedClients || !createdAt || !lastActivityAt || !path3) {
|
|
424
|
-
return [];
|
|
425
|
-
}
|
|
426
|
-
return [
|
|
427
|
-
{
|
|
428
|
-
name,
|
|
429
|
-
windows: Number(windows),
|
|
430
|
-
attachedClients: Number(attachedClients),
|
|
431
|
-
createdAt: Number(createdAt),
|
|
432
|
-
lastActivityAt: Number(lastActivityAt),
|
|
433
|
-
path: path3,
|
|
434
|
-
currentCommand
|
|
435
|
-
}
|
|
436
|
-
];
|
|
437
|
-
});
|
|
438
|
-
}
|
|
439
|
-
function formatPreview(stdout) {
|
|
440
|
-
const lines = stdout.replaceAll("\r", "").split("\n").map((line) => line.trimEnd()).filter((line) => line.length > 0).slice(-3);
|
|
441
|
-
if (lines.length > 0) {
|
|
442
|
-
return lines;
|
|
443
|
-
}
|
|
444
|
-
return ["Fresh session. Nothing has run yet."];
|
|
445
|
-
}
|
|
446
|
-
function isTmuxEmptyStateMessage(message) {
|
|
447
|
-
return TMUX_EMPTY_STATE_MARKERS.some((marker) => message.includes(marker));
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
// src/terminal.ts
|
|
451
|
-
async function createTerminalBridge(options) {
|
|
452
|
-
const {
|
|
453
|
-
tmux,
|
|
454
|
-
sessionName,
|
|
455
|
-
cols = DEFAULT_TERMINAL_SIZE.cols,
|
|
456
|
-
rows = DEFAULT_TERMINAL_SIZE.rows,
|
|
457
|
-
onData,
|
|
458
|
-
onExit
|
|
459
|
-
} = options;
|
|
460
|
-
assertValidSessionName(sessionName);
|
|
461
|
-
await tmux.createSession(sessionName);
|
|
462
|
-
const ptyProcess = spawn(
|
|
463
|
-
"tmux",
|
|
464
|
-
["-L", tmux.socketName, "attach-session", "-t", sessionName],
|
|
465
|
-
{
|
|
466
|
-
cols,
|
|
467
|
-
rows,
|
|
468
|
-
cwd: tmux.workspaceRoot,
|
|
469
|
-
env: {
|
|
470
|
-
...process.env,
|
|
471
|
-
TERM: "xterm-256color"
|
|
472
|
-
},
|
|
473
|
-
name: "xterm-256color"
|
|
474
|
-
}
|
|
475
|
-
);
|
|
476
|
-
ptyProcess.onData(onData);
|
|
477
|
-
ptyProcess.onExit(({ exitCode }) => {
|
|
478
|
-
onExit(exitCode);
|
|
479
|
-
});
|
|
480
|
-
return {
|
|
481
|
-
write(data) {
|
|
482
|
-
ptyProcess.write(data);
|
|
483
|
-
},
|
|
484
|
-
resize(nextCols, nextRows) {
|
|
485
|
-
ptyProcess.resize(nextCols, nextRows);
|
|
486
|
-
},
|
|
487
|
-
dispose() {
|
|
488
|
-
ptyProcess.kill();
|
|
489
|
-
}
|
|
490
|
-
};
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
// src/run-wrapper.ts
|
|
494
|
-
import { spawn as spawn2 } from "node-pty";
|
|
495
|
-
var STATUS_DEBOUNCE_MS = 300;
|
|
496
|
-
var OUTPUT_BUFFER_MAX_LINES = 20;
|
|
497
|
-
var CLAUDE_APPROVAL_PATTERNS = [
|
|
498
|
-
/do you want to/i,
|
|
499
|
-
/\ballow\b/i,
|
|
500
|
-
/\bdeny\b/i,
|
|
501
|
-
/\bpermission\b/i,
|
|
502
|
-
/proceed\?/i
|
|
503
|
-
];
|
|
504
|
-
var CLAUDE_INPUT_PATTERNS = [
|
|
505
|
-
/^>\s*$/m,
|
|
506
|
-
/❯/,
|
|
507
|
-
/\$ $/m
|
|
508
|
-
];
|
|
509
|
-
var CODEX_APPROVAL_PATTERNS = [
|
|
510
|
-
/apply changes/i,
|
|
511
|
-
/\[y\/n\]/i,
|
|
512
|
-
/\bapprove\b/i
|
|
513
|
-
];
|
|
514
|
-
var CODEX_INPUT_PATTERNS = [
|
|
515
|
-
/what would you like/i,
|
|
516
|
-
/❯/,
|
|
517
|
-
/^>\s*$/m
|
|
518
|
-
];
|
|
519
|
-
function matchesAny(text, patterns) {
|
|
520
|
-
return patterns.some((pattern) => pattern.test(text));
|
|
521
|
-
}
|
|
522
|
-
var RunWrapper = class {
|
|
523
|
-
runId;
|
|
524
|
-
tool;
|
|
525
|
-
repoPath;
|
|
526
|
-
prompt;
|
|
527
|
-
tmux;
|
|
528
|
-
onEvent;
|
|
529
|
-
onOutput;
|
|
530
|
-
ptyProcess = null;
|
|
531
|
-
currentStatus = "starting";
|
|
532
|
-
outputBuffer = [];
|
|
533
|
-
debounceTimer = null;
|
|
534
|
-
disposed = false;
|
|
535
|
-
sessionName;
|
|
536
|
-
constructor(options) {
|
|
537
|
-
this.runId = options.runId;
|
|
538
|
-
this.tool = options.tool;
|
|
539
|
-
this.repoPath = options.repoPath;
|
|
540
|
-
this.prompt = options.prompt;
|
|
541
|
-
this.tmux = options.tmux;
|
|
542
|
-
this.onEvent = options.onEvent;
|
|
543
|
-
this.onOutput = options.onOutput;
|
|
544
|
-
const shortId = this.runId.slice(0, 8);
|
|
545
|
-
this.sessionName = `run-${shortId}`;
|
|
546
|
-
}
|
|
547
|
-
async start() {
|
|
548
|
-
if (this.disposed) {
|
|
549
|
-
return;
|
|
550
|
-
}
|
|
551
|
-
this.emitStatus("starting");
|
|
552
|
-
await this.tmux.createSession(this.sessionName);
|
|
553
|
-
const command = this.buildCommand();
|
|
554
|
-
const ptyProcess = spawn2(
|
|
555
|
-
"tmux",
|
|
556
|
-
["-L", this.tmux.socketName, "attach-session", "-t", this.sessionName],
|
|
557
|
-
{
|
|
558
|
-
cols: 120,
|
|
559
|
-
rows: 36,
|
|
560
|
-
cwd: this.repoPath,
|
|
561
|
-
env: {
|
|
562
|
-
...process.env,
|
|
563
|
-
TERM: "xterm-256color"
|
|
564
|
-
},
|
|
565
|
-
name: "xterm-256color"
|
|
566
|
-
}
|
|
567
|
-
);
|
|
568
|
-
this.ptyProcess = ptyProcess;
|
|
569
|
-
ptyProcess.onData((data) => {
|
|
570
|
-
if (this.disposed) {
|
|
571
|
-
return;
|
|
572
|
-
}
|
|
573
|
-
this.onOutput(data);
|
|
574
|
-
this.appendToBuffer(data);
|
|
575
|
-
this.scheduleStatusDetection();
|
|
576
|
-
});
|
|
577
|
-
ptyProcess.onExit(({ exitCode }) => {
|
|
578
|
-
if (this.disposed) {
|
|
579
|
-
return;
|
|
580
|
-
}
|
|
581
|
-
if (this.debounceTimer) {
|
|
582
|
-
clearTimeout(this.debounceTimer);
|
|
583
|
-
this.debounceTimer = null;
|
|
584
|
-
}
|
|
585
|
-
if (exitCode === 0) {
|
|
586
|
-
this.emitStatus("success");
|
|
587
|
-
} else {
|
|
588
|
-
this.emitStatus("failed");
|
|
589
|
-
}
|
|
590
|
-
this.ptyProcess = null;
|
|
591
|
-
});
|
|
592
|
-
setTimeout(() => {
|
|
593
|
-
if (this.ptyProcess && !this.disposed) {
|
|
594
|
-
this.ptyProcess.write(command + "\n");
|
|
595
|
-
this.emitStatus("running");
|
|
596
|
-
}
|
|
597
|
-
}, 500);
|
|
598
|
-
}
|
|
599
|
-
sendInput(input) {
|
|
600
|
-
if (this.ptyProcess && !this.disposed) {
|
|
601
|
-
this.ptyProcess.write(input);
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
interrupt() {
|
|
605
|
-
if (this.ptyProcess && !this.disposed) {
|
|
606
|
-
this.ptyProcess.write("");
|
|
607
|
-
this.emitStatus("interrupted");
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
approve() {
|
|
611
|
-
if (this.ptyProcess && !this.disposed) {
|
|
612
|
-
this.ptyProcess.write("y\n");
|
|
613
|
-
}
|
|
614
|
-
}
|
|
615
|
-
reject() {
|
|
616
|
-
if (this.ptyProcess && !this.disposed) {
|
|
617
|
-
this.ptyProcess.write("n\n");
|
|
618
|
-
}
|
|
619
|
-
}
|
|
620
|
-
dispose() {
|
|
621
|
-
if (this.disposed) {
|
|
622
|
-
return;
|
|
623
|
-
}
|
|
624
|
-
this.disposed = true;
|
|
625
|
-
if (this.debounceTimer) {
|
|
626
|
-
clearTimeout(this.debounceTimer);
|
|
627
|
-
this.debounceTimer = null;
|
|
628
|
-
}
|
|
629
|
-
if (this.ptyProcess) {
|
|
630
|
-
this.ptyProcess.kill();
|
|
631
|
-
this.ptyProcess = null;
|
|
632
|
-
}
|
|
633
|
-
this.tmux.killSession(this.sessionName).catch(() => {
|
|
634
|
-
});
|
|
635
|
-
}
|
|
636
|
-
buildCommand() {
|
|
637
|
-
const escapedPrompt = this.prompt.replace(/'/g, "'\\''");
|
|
638
|
-
switch (this.tool) {
|
|
639
|
-
case "claude":
|
|
640
|
-
return `cd '${this.repoPath.replace(/'/g, "'\\''")}' && claude '${escapedPrompt}'`;
|
|
641
|
-
case "codex":
|
|
642
|
-
return `cd '${this.repoPath.replace(/'/g, "'\\''")}' && codex '${escapedPrompt}'`;
|
|
643
|
-
}
|
|
644
|
-
}
|
|
645
|
-
appendToBuffer(data) {
|
|
646
|
-
const newLines = data.split("\n");
|
|
647
|
-
this.outputBuffer.push(...newLines);
|
|
648
|
-
if (this.outputBuffer.length > OUTPUT_BUFFER_MAX_LINES) {
|
|
649
|
-
this.outputBuffer = this.outputBuffer.slice(-OUTPUT_BUFFER_MAX_LINES);
|
|
650
|
-
}
|
|
651
|
-
}
|
|
652
|
-
scheduleStatusDetection() {
|
|
653
|
-
if (this.debounceTimer) {
|
|
654
|
-
clearTimeout(this.debounceTimer);
|
|
655
|
-
}
|
|
656
|
-
this.debounceTimer = setTimeout(() => {
|
|
657
|
-
this.debounceTimer = null;
|
|
658
|
-
this.detectStatus();
|
|
659
|
-
}, STATUS_DEBOUNCE_MS);
|
|
660
|
-
}
|
|
661
|
-
detectStatus() {
|
|
662
|
-
if (this.disposed) {
|
|
663
|
-
return;
|
|
664
|
-
}
|
|
665
|
-
if (this.currentStatus === "success" || this.currentStatus === "failed" || this.currentStatus === "interrupted") {
|
|
666
|
-
return;
|
|
667
|
-
}
|
|
668
|
-
const recentText = this.outputBuffer.join("\n");
|
|
669
|
-
const detectedStatus = this.detectStatusFromText(recentText);
|
|
670
|
-
if (detectedStatus && detectedStatus !== this.currentStatus) {
|
|
671
|
-
this.emitStatus(detectedStatus);
|
|
672
|
-
}
|
|
673
|
-
}
|
|
674
|
-
detectStatusFromText(text) {
|
|
675
|
-
const approvalPatterns = this.tool === "claude" ? CLAUDE_APPROVAL_PATTERNS : CODEX_APPROVAL_PATTERNS;
|
|
676
|
-
if (matchesAny(text, approvalPatterns)) {
|
|
677
|
-
return "waiting_approval";
|
|
678
|
-
}
|
|
679
|
-
const inputPatterns = this.tool === "claude" ? CLAUDE_INPUT_PATTERNS : CODEX_INPUT_PATTERNS;
|
|
680
|
-
if (matchesAny(text, inputPatterns)) {
|
|
681
|
-
return "waiting_input";
|
|
682
|
-
}
|
|
683
|
-
if (text.trim().length > 0) {
|
|
684
|
-
return "running";
|
|
685
|
-
}
|
|
686
|
-
return null;
|
|
687
|
-
}
|
|
688
|
-
emitStatus(status, summary, hasDiff) {
|
|
689
|
-
this.currentStatus = status;
|
|
690
|
-
this.onEvent(status, summary, hasDiff);
|
|
691
|
-
}
|
|
692
|
-
};
|
|
693
|
-
|
|
694
|
-
// src/version.ts
|
|
695
|
-
import fs3 from "fs";
|
|
696
|
-
var packageMetadata = readAgentPackageMetadata();
|
|
697
|
-
var AGENT_PACKAGE_NAME = packageMetadata.name;
|
|
698
|
-
var AGENT_VERSION = packageMetadata.version;
|
|
699
|
-
function readAgentPackageMetadata() {
|
|
700
|
-
const packageJsonPath = new URL("../package.json", import.meta.url);
|
|
701
|
-
const raw = fs3.readFileSync(packageJsonPath, "utf-8");
|
|
702
|
-
const parsed = JSON.parse(raw);
|
|
703
|
-
if (!parsed.name || !parsed.version) {
|
|
704
|
-
throw new Error("Agent package metadata is missing name or version");
|
|
705
|
-
}
|
|
706
|
-
return {
|
|
707
|
-
name: parsed.name,
|
|
708
|
-
version: parsed.version
|
|
709
|
-
};
|
|
710
|
-
}
|
|
711
|
-
|
|
712
|
-
// src/connection.ts
|
|
713
|
-
var HEARTBEAT_INTERVAL_MS = 3e4;
|
|
714
|
-
var SESSION_SYNC_INTERVAL_MS = 15e3;
|
|
715
|
-
var INITIAL_RECONNECT_DELAY_MS = 1e3;
|
|
716
|
-
var MAX_RECONNECT_DELAY_MS = 3e4;
|
|
717
|
-
var defaultAgentRuntime = {
|
|
718
|
-
version: AGENT_VERSION,
|
|
719
|
-
serviceMode: process.env.WEBMUX_AGENT_SERVICE === "1",
|
|
720
|
-
autoUpgrade: process.env.WEBMUX_AGENT_AUTO_UPGRADE !== "0",
|
|
721
|
-
applyServiceUpgrade: ({ packageName, targetVersion }) => {
|
|
722
|
-
upgradeService({
|
|
723
|
-
agentName: process.env.WEBMUX_AGENT_NAME ?? "webmux-agent",
|
|
724
|
-
packageName,
|
|
725
|
-
version: targetVersion
|
|
726
|
-
});
|
|
727
|
-
},
|
|
728
|
-
exit: (code) => {
|
|
729
|
-
process.exit(code);
|
|
730
|
-
}
|
|
731
|
-
};
|
|
732
|
-
var AgentConnection = class {
|
|
733
|
-
serverUrl;
|
|
734
|
-
agentId;
|
|
735
|
-
agentSecret;
|
|
736
|
-
tmux;
|
|
737
|
-
runtime;
|
|
738
|
-
ws = null;
|
|
739
|
-
heartbeatTimer = null;
|
|
740
|
-
sessionSyncTimer = null;
|
|
741
|
-
reconnectTimer = null;
|
|
742
|
-
reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
|
|
743
|
-
bridges = /* @__PURE__ */ new Map();
|
|
744
|
-
runs = /* @__PURE__ */ new Map();
|
|
745
|
-
stopped = false;
|
|
746
|
-
constructor(serverUrl, agentId, agentSecret, tmux, runtime = defaultAgentRuntime) {
|
|
747
|
-
this.serverUrl = serverUrl;
|
|
748
|
-
this.agentId = agentId;
|
|
749
|
-
this.agentSecret = agentSecret;
|
|
750
|
-
this.tmux = tmux;
|
|
751
|
-
this.runtime = runtime;
|
|
752
|
-
}
|
|
753
|
-
start() {
|
|
754
|
-
this.stopped = false;
|
|
755
|
-
this.connect();
|
|
756
|
-
}
|
|
757
|
-
stop() {
|
|
758
|
-
this.stopped = true;
|
|
759
|
-
if (this.reconnectTimer) {
|
|
760
|
-
clearTimeout(this.reconnectTimer);
|
|
761
|
-
this.reconnectTimer = null;
|
|
762
|
-
}
|
|
763
|
-
this.stopHeartbeat();
|
|
764
|
-
this.stopSessionSync();
|
|
765
|
-
this.disposeAllBridges();
|
|
766
|
-
this.disposeAllRuns();
|
|
767
|
-
if (this.ws) {
|
|
768
|
-
this.ws.close(1e3, "agent shutting down");
|
|
769
|
-
this.ws = null;
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
connect() {
|
|
773
|
-
const wsUrl = buildWsUrl(this.serverUrl);
|
|
774
|
-
console.log(`[agent] Connecting to ${wsUrl}`);
|
|
775
|
-
const ws = new WebSocket(wsUrl);
|
|
776
|
-
this.ws = ws;
|
|
777
|
-
ws.on("open", () => {
|
|
778
|
-
console.log("[agent] WebSocket connected, authenticating...");
|
|
779
|
-
this.reconnectDelay = INITIAL_RECONNECT_DELAY_MS;
|
|
780
|
-
this.sendMessage({
|
|
781
|
-
type: "auth",
|
|
782
|
-
agentId: this.agentId,
|
|
783
|
-
agentSecret: this.agentSecret,
|
|
784
|
-
version: this.runtime.version
|
|
77
|
+
response = await fetch(`${serverUrl}/api/agents/register`, {
|
|
78
|
+
method: "POST",
|
|
79
|
+
headers: { "Content-Type": "application/json" },
|
|
80
|
+
body: JSON.stringify(body)
|
|
785
81
|
});
|
|
786
|
-
})
|
|
787
|
-
|
|
788
|
-
|
|
82
|
+
} catch (err) {
|
|
83
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
84
|
+
console.error(`[agent] Failed to connect to server: ${message}`);
|
|
85
|
+
process.exit(1);
|
|
86
|
+
}
|
|
87
|
+
if (!response.ok) {
|
|
88
|
+
let errorMessage;
|
|
789
89
|
try {
|
|
790
|
-
|
|
90
|
+
const errorBody = await response.json();
|
|
91
|
+
errorMessage = errorBody.error ?? response.statusText;
|
|
791
92
|
} catch {
|
|
792
|
-
|
|
793
|
-
return;
|
|
93
|
+
errorMessage = response.statusText;
|
|
794
94
|
}
|
|
795
|
-
|
|
95
|
+
console.error(`[agent] Registration failed: ${errorMessage}`);
|
|
96
|
+
process.exit(1);
|
|
97
|
+
}
|
|
98
|
+
const result = await response.json();
|
|
99
|
+
saveCredentials({
|
|
100
|
+
serverUrl,
|
|
101
|
+
agentId: result.agentId,
|
|
102
|
+
agentSecret: result.agentSecret,
|
|
103
|
+
name: agentName
|
|
796
104
|
});
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
105
|
+
console.log(`[agent] Registration successful!`);
|
|
106
|
+
console.log(`[agent] Agent ID: ${result.agentId}`);
|
|
107
|
+
console.log(`[agent] Credentials saved to ${credentialsPath()}`);
|
|
108
|
+
console.log(``);
|
|
109
|
+
console.log(`Next steps:`);
|
|
110
|
+
console.log(` pnpm dlx @webmux/agent start # run once`);
|
|
111
|
+
console.log(` pnpm dlx @webmux/agent service install # install as managed systemd service`);
|
|
112
|
+
});
|
|
113
|
+
program.command("start").description("Start the agent and connect to the server").action(async () => {
|
|
114
|
+
const creds = loadCredentials();
|
|
115
|
+
if (!creds) {
|
|
116
|
+
console.error(
|
|
117
|
+
`[agent] No credentials found at ${credentialsPath()}. Run "npx @webmux/agent register" first.`
|
|
118
|
+
);
|
|
119
|
+
process.exit(1);
|
|
120
|
+
}
|
|
121
|
+
const { AgentConnection, TmuxClient } = await loadAgentRuntime();
|
|
122
|
+
console.log(`[agent] Starting agent "${creds.name}"...`);
|
|
123
|
+
console.log(`[agent] Server: ${creds.serverUrl}`);
|
|
124
|
+
console.log(`[agent] Agent ID: ${creds.agentId}`);
|
|
125
|
+
const tmux = new TmuxClient({
|
|
126
|
+
socketName: "webmux",
|
|
127
|
+
workspaceRoot: os2.homedir()
|
|
803
128
|
});
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
case "terminal-detach":
|
|
832
|
-
this.handleTerminalDetach(msg.browserId);
|
|
833
|
-
break;
|
|
834
|
-
case "terminal-input":
|
|
835
|
-
this.handleTerminalInput(msg.browserId, msg.data);
|
|
836
|
-
break;
|
|
837
|
-
case "terminal-resize":
|
|
838
|
-
this.handleTerminalResize(msg.browserId, msg.cols, msg.rows);
|
|
839
|
-
break;
|
|
840
|
-
case "session-create":
|
|
841
|
-
this.handleSessionCreate(msg.requestId, msg.name);
|
|
842
|
-
break;
|
|
843
|
-
case "session-kill":
|
|
844
|
-
this.handleSessionKill(msg.requestId, msg.name);
|
|
845
|
-
break;
|
|
846
|
-
case "run-start":
|
|
847
|
-
this.handleRunStart(msg.runId, msg.tool, msg.repoPath, msg.prompt);
|
|
848
|
-
break;
|
|
849
|
-
case "run-input":
|
|
850
|
-
this.handleRunInput(msg.runId, msg.input);
|
|
851
|
-
break;
|
|
852
|
-
case "run-interrupt":
|
|
853
|
-
this.handleRunInterrupt(msg.runId);
|
|
854
|
-
break;
|
|
855
|
-
case "run-approve":
|
|
856
|
-
this.handleRunApprove(msg.runId);
|
|
857
|
-
break;
|
|
858
|
-
case "run-reject":
|
|
859
|
-
this.handleRunReject(msg.runId);
|
|
860
|
-
break;
|
|
861
|
-
default:
|
|
862
|
-
console.warn("[agent] Unknown message type:", msg.type);
|
|
863
|
-
}
|
|
864
|
-
}
|
|
865
|
-
async syncSessions() {
|
|
866
|
-
try {
|
|
867
|
-
const sessions = await this.tmux.listSessions();
|
|
868
|
-
this.sendMessage({ type: "sessions-sync", sessions });
|
|
869
|
-
return sessions;
|
|
870
|
-
} catch (err) {
|
|
871
|
-
console.error("[agent] Failed to list sessions:", err);
|
|
872
|
-
this.sendMessage({ type: "error", message: "Failed to list sessions" });
|
|
873
|
-
return [];
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
async handleTerminalAttach(browserId, sessionName, cols, rows) {
|
|
877
|
-
const existing = this.bridges.get(browserId);
|
|
878
|
-
if (existing) {
|
|
879
|
-
existing.dispose();
|
|
880
|
-
this.bridges.delete(browserId);
|
|
881
|
-
}
|
|
129
|
+
const connection = new AgentConnection(
|
|
130
|
+
creds.serverUrl,
|
|
131
|
+
creds.agentId,
|
|
132
|
+
creds.agentSecret,
|
|
133
|
+
tmux
|
|
134
|
+
);
|
|
135
|
+
const shutdown = () => {
|
|
136
|
+
console.log("\n[agent] Shutting down...");
|
|
137
|
+
connection.stop();
|
|
138
|
+
process.exit(0);
|
|
139
|
+
};
|
|
140
|
+
process.on("SIGINT", shutdown);
|
|
141
|
+
process.on("SIGTERM", shutdown);
|
|
142
|
+
connection.start();
|
|
143
|
+
});
|
|
144
|
+
program.command("status").description("Show agent status and credentials info").action(() => {
|
|
145
|
+
const creds = loadCredentials();
|
|
146
|
+
if (!creds) {
|
|
147
|
+
console.log(`[agent] Not registered. No credentials found at ${credentialsPath()}.`);
|
|
148
|
+
process.exit(0);
|
|
149
|
+
}
|
|
150
|
+
console.log(`Agent Name: ${creds.name}`);
|
|
151
|
+
console.log(`Agent Version: ${AGENT_VERSION}`);
|
|
152
|
+
console.log(`Server URL: ${creds.serverUrl}`);
|
|
153
|
+
console.log(`Agent ID: ${creds.agentId}`);
|
|
154
|
+
console.log(`Credentials File: ${credentialsPath()}`);
|
|
155
|
+
const installedService = readInstalledServiceConfig();
|
|
882
156
|
try {
|
|
883
|
-
const
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
rows,
|
|
888
|
-
onData: (data) => {
|
|
889
|
-
this.sendMessage({ type: "terminal-output", browserId, data });
|
|
890
|
-
},
|
|
891
|
-
onExit: (exitCode) => {
|
|
892
|
-
this.bridges.delete(browserId);
|
|
893
|
-
this.sendMessage({ type: "terminal-exit", browserId, exitCode });
|
|
894
|
-
void this.syncSessions();
|
|
895
|
-
}
|
|
896
|
-
});
|
|
897
|
-
this.bridges.set(browserId, bridge);
|
|
898
|
-
this.sendMessage({ type: "terminal-ready", browserId, sessionName });
|
|
899
|
-
await this.syncSessions();
|
|
900
|
-
} catch (err) {
|
|
901
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
902
|
-
console.error(`[agent] Failed to attach terminal for browser ${browserId}:`, message);
|
|
903
|
-
this.sendMessage({ type: "error", browserId, message: `Failed to attach: ${message}` });
|
|
157
|
+
const result = execFileSync("systemctl", ["--user", "is-active", SERVICE_NAME], { encoding: "utf-8" }).trim();
|
|
158
|
+
console.log(`Service: ${result}`);
|
|
159
|
+
} catch {
|
|
160
|
+
console.log(`Service: not installed`);
|
|
904
161
|
}
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
const bridge = this.bridges.get(browserId);
|
|
908
|
-
if (bridge) {
|
|
909
|
-
bridge.dispose();
|
|
910
|
-
this.bridges.delete(browserId);
|
|
911
|
-
void this.syncSessions();
|
|
162
|
+
if (installedService?.version) {
|
|
163
|
+
console.log(`Service Version: ${installedService.version}`);
|
|
912
164
|
}
|
|
913
|
-
}
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
handleTerminalResize(browserId, cols, rows) {
|
|
921
|
-
const bridge = this.bridges.get(browserId);
|
|
922
|
-
if (bridge) {
|
|
923
|
-
bridge.resize(cols, rows);
|
|
165
|
+
});
|
|
166
|
+
const service = program.command("service").description("Manage the systemd service");
|
|
167
|
+
service.command("install").description("Install and start the agent as a managed systemd user service").option("--no-auto-upgrade", "Disable automatic upgrades for the managed service").action((opts) => {
|
|
168
|
+
const creds = loadCredentials();
|
|
169
|
+
if (!creds) {
|
|
170
|
+
console.error(`[agent] Not registered. Run "npx @webmux/agent register" first.`);
|
|
171
|
+
process.exit(1);
|
|
924
172
|
}
|
|
925
|
-
}
|
|
926
|
-
async handleSessionCreate(requestId, name) {
|
|
927
173
|
try {
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
}
|
|
934
|
-
|
|
174
|
+
installService({
|
|
175
|
+
agentName: creds.name,
|
|
176
|
+
packageName: AGENT_PACKAGE_NAME,
|
|
177
|
+
version: AGENT_VERSION,
|
|
178
|
+
autoUpgrade: opts.autoUpgrade
|
|
179
|
+
});
|
|
180
|
+
console.log(``);
|
|
181
|
+
console.log(`[agent] Service installed and started!`);
|
|
182
|
+
console.log(`[agent] Managed version: ${AGENT_VERSION}`);
|
|
183
|
+
console.log(`[agent] It will auto-start on boot.`);
|
|
184
|
+
console.log(``);
|
|
185
|
+
console.log(`Useful commands:`);
|
|
186
|
+
console.log(` systemctl --user status ${SERVICE_NAME}`);
|
|
187
|
+
console.log(` journalctl --user -u ${SERVICE_NAME} -f`);
|
|
188
|
+
console.log(` pnpm dlx @webmux/agent service upgrade --to <version>`);
|
|
189
|
+
console.log(` pnpm dlx @webmux/agent service uninstall`);
|
|
935
190
|
} catch (err) {
|
|
936
191
|
const message = err instanceof Error ? err.message : String(err);
|
|
937
|
-
console.error(`[agent] Failed to
|
|
938
|
-
|
|
192
|
+
console.error(`[agent] Failed to install managed service: ${message}`);
|
|
193
|
+
console.error(`[agent] Service file path: ${servicePath()}`);
|
|
194
|
+
process.exit(1);
|
|
939
195
|
}
|
|
940
|
-
}
|
|
941
|
-
|
|
196
|
+
});
|
|
197
|
+
service.command("uninstall").description("Stop and remove the systemd user service").action(() => {
|
|
942
198
|
try {
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
199
|
+
uninstallService();
|
|
200
|
+
console.log(`[agent] Service file removed: ${servicePath()}`);
|
|
201
|
+
console.log(`[agent] Service uninstalled.`);
|
|
946
202
|
} catch (err) {
|
|
947
203
|
const message = err instanceof Error ? err.message : String(err);
|
|
948
|
-
console.error(`[agent] Failed to
|
|
949
|
-
|
|
950
|
-
}
|
|
951
|
-
}
|
|
952
|
-
sendMessage(msg) {
|
|
953
|
-
if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
|
954
|
-
this.ws.send(JSON.stringify(msg));
|
|
204
|
+
console.error(`[agent] Failed to uninstall service: ${message}`);
|
|
205
|
+
process.exit(1);
|
|
955
206
|
}
|
|
956
|
-
}
|
|
957
|
-
|
|
958
|
-
const targetVersion = upgradePolicy?.targetVersion;
|
|
959
|
-
if (!targetVersion) {
|
|
960
|
-
return false;
|
|
961
|
-
}
|
|
962
|
-
let comparison;
|
|
207
|
+
});
|
|
208
|
+
service.command("status").description("Show systemd service status").action(() => {
|
|
963
209
|
try {
|
|
964
|
-
|
|
210
|
+
execFileSync("systemctl", ["--user", "status", SERVICE_NAME], { stdio: "inherit" });
|
|
965
211
|
} catch {
|
|
966
|
-
console.
|
|
967
|
-
return false;
|
|
968
|
-
}
|
|
969
|
-
if (comparison >= 0) {
|
|
970
|
-
return false;
|
|
212
|
+
console.log(`[agent] Service is not installed or not running.`);
|
|
971
213
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
214
|
+
});
|
|
215
|
+
service.command("upgrade").description("Switch the managed service to a specific agent version and restart it").requiredOption("--to <version>", "Target agent version (for example 0.1.5)").action((opts) => {
|
|
216
|
+
const creds = loadCredentials();
|
|
217
|
+
if (!creds) {
|
|
218
|
+
console.error(`[agent] Not registered. Run "npx @webmux/agent register" first.`);
|
|
219
|
+
process.exit(1);
|
|
977
220
|
}
|
|
221
|
+
console.log(`[agent] Switching managed service to ${opts.to}...`);
|
|
978
222
|
try {
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
223
|
+
upgradeService({
|
|
224
|
+
agentName: creds.name,
|
|
225
|
+
packageName: AGENT_PACKAGE_NAME,
|
|
226
|
+
version: opts.to
|
|
982
227
|
});
|
|
983
|
-
console.log(
|
|
228
|
+
console.log("[agent] Managed service updated successfully.");
|
|
984
229
|
} catch (err) {
|
|
985
230
|
const message = err instanceof Error ? err.message : String(err);
|
|
986
|
-
console.error(`[agent] Failed to
|
|
987
|
-
|
|
988
|
-
return false;
|
|
989
|
-
}
|
|
990
|
-
this.stop();
|
|
991
|
-
this.runtime.exit(0);
|
|
992
|
-
return true;
|
|
993
|
-
}
|
|
994
|
-
startHeartbeat() {
|
|
995
|
-
this.stopHeartbeat();
|
|
996
|
-
this.heartbeatTimer = setInterval(() => {
|
|
997
|
-
this.sendMessage({ type: "heartbeat" });
|
|
998
|
-
}, HEARTBEAT_INTERVAL_MS);
|
|
999
|
-
}
|
|
1000
|
-
startSessionSync() {
|
|
1001
|
-
this.stopSessionSync();
|
|
1002
|
-
this.sessionSyncTimer = setInterval(() => {
|
|
1003
|
-
void this.syncSessions();
|
|
1004
|
-
}, SESSION_SYNC_INTERVAL_MS);
|
|
1005
|
-
}
|
|
1006
|
-
stopHeartbeat() {
|
|
1007
|
-
if (this.heartbeatTimer) {
|
|
1008
|
-
clearInterval(this.heartbeatTimer);
|
|
1009
|
-
this.heartbeatTimer = null;
|
|
1010
|
-
}
|
|
1011
|
-
}
|
|
1012
|
-
stopSessionSync() {
|
|
1013
|
-
if (this.sessionSyncTimer) {
|
|
1014
|
-
clearInterval(this.sessionSyncTimer);
|
|
1015
|
-
this.sessionSyncTimer = null;
|
|
1016
|
-
}
|
|
1017
|
-
}
|
|
1018
|
-
disposeAllBridges() {
|
|
1019
|
-
for (const [browserId, bridge] of this.bridges) {
|
|
1020
|
-
bridge.dispose();
|
|
1021
|
-
this.bridges.delete(browserId);
|
|
1022
|
-
}
|
|
1023
|
-
}
|
|
1024
|
-
disposeAllRuns() {
|
|
1025
|
-
for (const [runId, run] of this.runs) {
|
|
1026
|
-
run.dispose();
|
|
1027
|
-
this.runs.delete(runId);
|
|
1028
|
-
}
|
|
1029
|
-
}
|
|
1030
|
-
handleRunStart(runId, tool, repoPath, prompt) {
|
|
1031
|
-
const existing = this.runs.get(runId);
|
|
1032
|
-
if (existing) {
|
|
1033
|
-
existing.dispose();
|
|
1034
|
-
this.runs.delete(runId);
|
|
1035
|
-
}
|
|
1036
|
-
const run = new RunWrapper({
|
|
1037
|
-
runId,
|
|
1038
|
-
tool,
|
|
1039
|
-
repoPath,
|
|
1040
|
-
prompt,
|
|
1041
|
-
tmux: this.tmux,
|
|
1042
|
-
onEvent: (status, summary, hasDiff) => {
|
|
1043
|
-
this.sendMessage({ type: "run-event", runId, status, summary, hasDiff });
|
|
1044
|
-
},
|
|
1045
|
-
onOutput: (data) => {
|
|
1046
|
-
this.sendMessage({ type: "run-output", runId, data });
|
|
1047
|
-
}
|
|
1048
|
-
});
|
|
1049
|
-
this.runs.set(runId, run);
|
|
1050
|
-
run.start().catch((err) => {
|
|
1051
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1052
|
-
console.error(`[agent] Failed to start run ${runId}:`, message);
|
|
1053
|
-
this.sendMessage({
|
|
1054
|
-
type: "run-event",
|
|
1055
|
-
runId,
|
|
1056
|
-
status: "failed",
|
|
1057
|
-
summary: `Failed to start: ${message}`
|
|
1058
|
-
});
|
|
1059
|
-
this.runs.delete(runId);
|
|
1060
|
-
});
|
|
1061
|
-
}
|
|
1062
|
-
handleRunInput(runId, input) {
|
|
1063
|
-
const run = this.runs.get(runId);
|
|
1064
|
-
if (run) {
|
|
1065
|
-
run.sendInput(input);
|
|
1066
|
-
} else {
|
|
1067
|
-
console.warn(`[agent] run-input: no run found for ${runId}`);
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
handleRunInterrupt(runId) {
|
|
1071
|
-
const run = this.runs.get(runId);
|
|
1072
|
-
if (run) {
|
|
1073
|
-
run.interrupt();
|
|
1074
|
-
} else {
|
|
1075
|
-
console.warn(`[agent] run-interrupt: no run found for ${runId}`);
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
|
-
handleRunApprove(runId) {
|
|
1079
|
-
const run = this.runs.get(runId);
|
|
1080
|
-
if (run) {
|
|
1081
|
-
run.approve();
|
|
1082
|
-
} else {
|
|
1083
|
-
console.warn(`[agent] run-approve: no run found for ${runId}`);
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
handleRunReject(runId) {
|
|
1087
|
-
const run = this.runs.get(runId);
|
|
1088
|
-
if (run) {
|
|
1089
|
-
run.reject();
|
|
1090
|
-
} else {
|
|
1091
|
-
console.warn(`[agent] run-reject: no run found for ${runId}`);
|
|
231
|
+
console.error(`[agent] Failed to upgrade managed service: ${message}`);
|
|
232
|
+
process.exit(1);
|
|
1092
233
|
}
|
|
1093
|
-
}
|
|
1094
|
-
onDisconnect() {
|
|
1095
|
-
this.stopHeartbeat();
|
|
1096
|
-
this.stopSessionSync();
|
|
1097
|
-
this.disposeAllBridges();
|
|
1098
|
-
this.ws = null;
|
|
1099
|
-
if (this.stopped) {
|
|
1100
|
-
return;
|
|
1101
|
-
}
|
|
1102
|
-
console.log(`[agent] Reconnecting in ${this.reconnectDelay}ms...`);
|
|
1103
|
-
this.reconnectTimer = setTimeout(() => {
|
|
1104
|
-
this.reconnectTimer = null;
|
|
1105
|
-
this.connect();
|
|
1106
|
-
}, this.reconnectDelay);
|
|
1107
|
-
this.reconnectDelay = Math.min(this.reconnectDelay * 2, MAX_RECONNECT_DELAY_MS);
|
|
1108
|
-
}
|
|
1109
|
-
};
|
|
1110
|
-
function buildWsUrl(serverUrl) {
|
|
1111
|
-
const url = new URL("/ws/agent", serverUrl);
|
|
1112
|
-
url.protocol = url.protocol === "https:" ? "wss:" : "ws:";
|
|
1113
|
-
return url.toString();
|
|
1114
|
-
}
|
|
1115
|
-
|
|
1116
|
-
// src/cli.ts
|
|
1117
|
-
var program = new Command();
|
|
1118
|
-
program.name("webmux-agent").description("Webmux agent \u2014 connects your machine to the webmux server").version(AGENT_VERSION);
|
|
1119
|
-
program.command("register").description("Register this agent with a webmux server").requiredOption("--server <url>", "Server URL (e.g. https://webmux.example.com)").requiredOption("--token <token>", "One-time registration token from the server").option("--name <name>", "Display name for this agent (defaults to hostname)").action(async (opts) => {
|
|
1120
|
-
const serverUrl = opts.server.replace(/\/+$/, "");
|
|
1121
|
-
const agentName = opts.name ?? os3.hostname();
|
|
1122
|
-
console.log(`[agent] Registering with server ${serverUrl}...`);
|
|
1123
|
-
console.log(`[agent] Agent name: ${agentName}`);
|
|
1124
|
-
const body = {
|
|
1125
|
-
token: opts.token,
|
|
1126
|
-
name: agentName
|
|
1127
|
-
};
|
|
1128
|
-
let response;
|
|
1129
|
-
try {
|
|
1130
|
-
response = await fetch(`${serverUrl}/api/agents/register`, {
|
|
1131
|
-
method: "POST",
|
|
1132
|
-
headers: { "Content-Type": "application/json" },
|
|
1133
|
-
body: JSON.stringify(body)
|
|
1134
|
-
});
|
|
1135
|
-
} catch (err) {
|
|
1136
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1137
|
-
console.error(`[agent] Failed to connect to server: ${message}`);
|
|
1138
|
-
process.exit(1);
|
|
1139
|
-
}
|
|
1140
|
-
if (!response.ok) {
|
|
1141
|
-
let errorMessage;
|
|
1142
|
-
try {
|
|
1143
|
-
const errorBody = await response.json();
|
|
1144
|
-
errorMessage = errorBody.error ?? response.statusText;
|
|
1145
|
-
} catch {
|
|
1146
|
-
errorMessage = response.statusText;
|
|
1147
|
-
}
|
|
1148
|
-
console.error(`[agent] Registration failed: ${errorMessage}`);
|
|
1149
|
-
process.exit(1);
|
|
1150
|
-
}
|
|
1151
|
-
const result = await response.json();
|
|
1152
|
-
saveCredentials({
|
|
1153
|
-
serverUrl,
|
|
1154
|
-
agentId: result.agentId,
|
|
1155
|
-
agentSecret: result.agentSecret,
|
|
1156
|
-
name: agentName
|
|
1157
|
-
});
|
|
1158
|
-
console.log(`[agent] Registration successful!`);
|
|
1159
|
-
console.log(`[agent] Agent ID: ${result.agentId}`);
|
|
1160
|
-
console.log(`[agent] Credentials saved to ${credentialsPath()}`);
|
|
1161
|
-
console.log(``);
|
|
1162
|
-
console.log(`Next steps:`);
|
|
1163
|
-
console.log(` pnpm dlx @webmux/agent start # run once`);
|
|
1164
|
-
console.log(` pnpm dlx @webmux/agent service install # install as managed systemd service`);
|
|
1165
|
-
});
|
|
1166
|
-
program.command("start").description("Start the agent and connect to the server").action(() => {
|
|
1167
|
-
const creds = loadCredentials();
|
|
1168
|
-
if (!creds) {
|
|
1169
|
-
console.error(
|
|
1170
|
-
`[agent] No credentials found at ${credentialsPath()}. Run "npx @webmux/agent register" first.`
|
|
1171
|
-
);
|
|
1172
|
-
process.exit(1);
|
|
1173
|
-
}
|
|
1174
|
-
console.log(`[agent] Starting agent "${creds.name}"...`);
|
|
1175
|
-
console.log(`[agent] Server: ${creds.serverUrl}`);
|
|
1176
|
-
console.log(`[agent] Agent ID: ${creds.agentId}`);
|
|
1177
|
-
const tmux = new TmuxClient({
|
|
1178
|
-
socketName: "webmux",
|
|
1179
|
-
workspaceRoot: os3.homedir()
|
|
1180
234
|
});
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
const
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
process.exit(0);
|
|
1191
|
-
};
|
|
1192
|
-
process.on("SIGINT", shutdown);
|
|
1193
|
-
process.on("SIGTERM", shutdown);
|
|
1194
|
-
connection.start();
|
|
1195
|
-
});
|
|
1196
|
-
program.command("status").description("Show agent status and credentials info").action(() => {
|
|
1197
|
-
const creds = loadCredentials();
|
|
1198
|
-
if (!creds) {
|
|
1199
|
-
console.log(`[agent] Not registered. No credentials found at ${credentialsPath()}.`);
|
|
1200
|
-
process.exit(0);
|
|
1201
|
-
}
|
|
1202
|
-
console.log(`Agent Name: ${creds.name}`);
|
|
1203
|
-
console.log(`Agent Version: ${AGENT_VERSION}`);
|
|
1204
|
-
console.log(`Server URL: ${creds.serverUrl}`);
|
|
1205
|
-
console.log(`Agent ID: ${creds.agentId}`);
|
|
1206
|
-
console.log(`Credentials File: ${credentialsPath()}`);
|
|
1207
|
-
const installedService = readInstalledServiceConfig();
|
|
1208
|
-
try {
|
|
1209
|
-
const result = execFileSync2("systemctl", ["--user", "is-active", SERVICE_NAME], { encoding: "utf-8" }).trim();
|
|
1210
|
-
console.log(`Service: ${result}`);
|
|
1211
|
-
} catch {
|
|
1212
|
-
console.log(`Service: not installed`);
|
|
1213
|
-
}
|
|
1214
|
-
if (installedService?.version) {
|
|
1215
|
-
console.log(`Service Version: ${installedService.version}`);
|
|
1216
|
-
}
|
|
1217
|
-
});
|
|
1218
|
-
var service = program.command("service").description("Manage the systemd service");
|
|
1219
|
-
service.command("install").description("Install and start the agent as a managed systemd user service").option("--no-auto-upgrade", "Disable automatic upgrades for the managed service").action((opts) => {
|
|
1220
|
-
const creds = loadCredentials();
|
|
1221
|
-
if (!creds) {
|
|
1222
|
-
console.error(`[agent] Not registered. Run "npx @webmux/agent register" first.`);
|
|
1223
|
-
process.exit(1);
|
|
1224
|
-
}
|
|
1225
|
-
try {
|
|
1226
|
-
installService({
|
|
1227
|
-
agentName: creds.name,
|
|
1228
|
-
packageName: AGENT_PACKAGE_NAME,
|
|
1229
|
-
version: AGENT_VERSION,
|
|
1230
|
-
autoUpgrade: opts.autoUpgrade
|
|
1231
|
-
});
|
|
1232
|
-
console.log(``);
|
|
1233
|
-
console.log(`[agent] Service installed and started!`);
|
|
1234
|
-
console.log(`[agent] Managed version: ${AGENT_VERSION}`);
|
|
1235
|
-
console.log(`[agent] It will auto-start on boot.`);
|
|
1236
|
-
console.log(``);
|
|
1237
|
-
console.log(`Useful commands:`);
|
|
1238
|
-
console.log(` systemctl --user status ${SERVICE_NAME}`);
|
|
1239
|
-
console.log(` journalctl --user -u ${SERVICE_NAME} -f`);
|
|
1240
|
-
console.log(` pnpm dlx @webmux/agent service upgrade --to <version>`);
|
|
1241
|
-
console.log(` pnpm dlx @webmux/agent service uninstall`);
|
|
1242
|
-
} catch (err) {
|
|
1243
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1244
|
-
console.error(`[agent] Failed to install managed service: ${message}`);
|
|
1245
|
-
console.error(`[agent] Service file path: ${servicePath()}`);
|
|
1246
|
-
process.exit(1);
|
|
1247
|
-
}
|
|
1248
|
-
});
|
|
1249
|
-
service.command("uninstall").description("Stop and remove the systemd user service").action(() => {
|
|
1250
|
-
try {
|
|
1251
|
-
uninstallService();
|
|
1252
|
-
console.log(`[agent] Service file removed: ${servicePath()}`);
|
|
1253
|
-
console.log(`[agent] Service uninstalled.`);
|
|
1254
|
-
} catch (err) {
|
|
1255
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1256
|
-
console.error(`[agent] Failed to uninstall service: ${message}`);
|
|
1257
|
-
process.exit(1);
|
|
1258
|
-
}
|
|
1259
|
-
});
|
|
1260
|
-
service.command("status").description("Show systemd service status").action(() => {
|
|
1261
|
-
try {
|
|
1262
|
-
execFileSync2("systemctl", ["--user", "status", SERVICE_NAME], { stdio: "inherit" });
|
|
1263
|
-
} catch {
|
|
1264
|
-
console.log(`[agent] Service is not installed or not running.`);
|
|
1265
|
-
}
|
|
1266
|
-
});
|
|
1267
|
-
service.command("upgrade").description("Switch the managed service to a specific agent version and restart it").requiredOption("--to <version>", "Target agent version (for example 0.1.5)").action((opts) => {
|
|
1268
|
-
const creds = loadCredentials();
|
|
1269
|
-
if (!creds) {
|
|
1270
|
-
console.error(`[agent] Not registered. Run "npx @webmux/agent register" first.`);
|
|
1271
|
-
process.exit(1);
|
|
1272
|
-
}
|
|
1273
|
-
console.log(`[agent] Switching managed service to ${opts.to}...`);
|
|
1274
|
-
try {
|
|
1275
|
-
upgradeService({
|
|
1276
|
-
agentName: creds.name,
|
|
1277
|
-
packageName: AGENT_PACKAGE_NAME,
|
|
1278
|
-
version: opts.to
|
|
1279
|
-
});
|
|
1280
|
-
console.log("[agent] Managed service updated successfully.");
|
|
1281
|
-
} catch (err) {
|
|
1282
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
1283
|
-
console.error(`[agent] Failed to upgrade managed service: ${message}`);
|
|
1284
|
-
process.exit(1);
|
|
235
|
+
return program;
|
|
236
|
+
}
|
|
237
|
+
async function run(argv = process.argv) {
|
|
238
|
+
await createProgram().parseAsync(argv);
|
|
239
|
+
}
|
|
240
|
+
function isDirectExecution() {
|
|
241
|
+
const entryPath = process.argv[1];
|
|
242
|
+
if (!entryPath) {
|
|
243
|
+
return false;
|
|
1285
244
|
}
|
|
1286
|
-
|
|
1287
|
-
|
|
245
|
+
return import.meta.url === pathToFileURL(entryPath).href;
|
|
246
|
+
}
|
|
247
|
+
if (isDirectExecution()) {
|
|
248
|
+
void run();
|
|
249
|
+
}
|
|
250
|
+
export {
|
|
251
|
+
createProgram,
|
|
252
|
+
run
|
|
253
|
+
};
|