@workermill/agent 0.8.8 → 0.8.9

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.
@@ -1,412 +0,0 @@
1
- /**
2
- * Setup Wizard — Interactive configuration for the WorkerMill Remote Agent.
3
- *
4
- * Prerequisites checked/installed:
5
- * - Docker Desktop (must be installed manually)
6
- * - Git for Windows (must be installed manually — required by Claude Code on Windows)
7
- * - Claude CLI → winget on Windows, curl | bash on Linux/macOS/WSL
8
- * - Claude auth → launches `claude` which prompts for sign-in on first run
9
- * - Worker image → `docker pull public.ecr.aws/a7k5r0v0/workermill-worker:latest`
10
- */
11
- import chalk from "chalk";
12
- import ora from "ora";
13
- import inquirer from "inquirer";
14
- import { execSync, spawnSync } from "child_process";
15
- import { existsSync } from "fs";
16
- import { hostname, homedir, totalmem, cpus } from "os";
17
- import { join } from "path";
18
- import axios from "axios";
19
- import { saveConfigToFile, getConfigFile, findClaudePath, } from "../config.js";
20
- const isWindows = process.platform === "win32";
21
- /**
22
- * Check if a command exists in PATH.
23
- */
24
- function commandExists(cmd) {
25
- try {
26
- const which = isWindows ? "where" : "which";
27
- execSync(`${which} ${cmd}`, { stdio: "ignore", timeout: 10000 });
28
- return true;
29
- }
30
- catch {
31
- return false;
32
- }
33
- }
34
- /**
35
- * Get version string from a command, or null.
36
- */
37
- function getVersion(cmd) {
38
- try {
39
- return execSync(`"${cmd}" --version`, { encoding: "utf-8", timeout: 10000 }).trim();
40
- }
41
- catch {
42
- return null;
43
- }
44
- }
45
- /**
46
- * Find Git Bash on Windows. Returns path to bash.exe or null.
47
- */
48
- function findGitBash() {
49
- if (!isWindows)
50
- return null; // Not needed on non-Windows
51
- // Check if git is in PATH (Git for Windows adds it)
52
- if (commandExists("git")) {
53
- try {
54
- const gitPath = execSync("where git", { encoding: "utf-8", timeout: 10000 }).trim().split("\n")[0];
55
- // git.exe is at Git/cmd/git.exe, bash.exe is at Git/bin/bash.exe
56
- const gitDir = join(gitPath, "..", "..", "bin", "bash.exe");
57
- if (existsSync(gitDir))
58
- return gitDir;
59
- }
60
- catch { /* ignore */ }
61
- }
62
- // Check common install locations
63
- const candidates = [
64
- join(process.env.ProgramFiles || "C:\\Program Files", "Git", "bin", "bash.exe"),
65
- "C:\\Program Files\\Git\\bin\\bash.exe",
66
- "C:\\Program Files (x86)\\Git\\bin\\bash.exe",
67
- join(homedir(), "scoop", "apps", "git", "current", "bin", "bash.exe"),
68
- ];
69
- for (const candidate of candidates) {
70
- if (existsSync(candidate))
71
- return candidate;
72
- }
73
- return null;
74
- }
75
- /**
76
- * Install Claude CLI using the official installer.
77
- * Returns true on success.
78
- */
79
- function installClaudeCli() {
80
- if (isWindows) {
81
- // Windows: use winget (built into Windows 10/11)
82
- try {
83
- execSync("winget install Anthropic.ClaudeCode --accept-package-agreements --accept-source-agreements", { stdio: "inherit", timeout: 180_000 });
84
- return true;
85
- }
86
- catch {
87
- return false;
88
- }
89
- }
90
- else {
91
- // macOS: try Homebrew first
92
- if (process.platform === "darwin") {
93
- try {
94
- execSync("brew install --cask claude-code", { stdio: "inherit", timeout: 180_000 });
95
- return true;
96
- }
97
- catch { /* fall through */ }
98
- }
99
- // Linux, WSL, macOS fallback: native installer
100
- try {
101
- execSync("curl -fsSL https://claude.ai/install.sh | bash", { stdio: "inherit", timeout: 120_000 });
102
- return true;
103
- }
104
- catch {
105
- return false;
106
- }
107
- }
108
- }
109
- export async function setupCommand() {
110
- // Welcome banner
111
- console.log();
112
- console.log(chalk.bold.cyan(" WorkerMill Remote Agent Setup"));
113
- console.log(chalk.dim(" ─────────────────────────────────────"));
114
- console.log();
115
- console.log(" Run AI workers locally with your Claude Max subscription.");
116
- console.log(" Workers execute on your machine, logs stream to the cloud dashboard.");
117
- console.log();
118
- // ── Step 0: System Requirements ───────────────────────────────────────────
119
- const totalRamGB = Math.round(totalmem() / (1024 * 1024 * 1024));
120
- const cpuCount = cpus().length;
121
- console.log(chalk.dim(" System"));
122
- console.log(` ${chalk.dim("RAM:")} ${totalRamGB} GB ${chalk.dim("CPUs:")} ${cpuCount}`);
123
- if (totalRamGB < 8) {
124
- console.log();
125
- console.log(chalk.red(" ✗ Insufficient RAM"));
126
- console.log(chalk.yellow(" WorkerMill requires at least 8 GB of RAM (16 GB recommended)."));
127
- console.log(chalk.yellow(` Your system has ${totalRamGB} GB.`));
128
- console.log();
129
- process.exit(1);
130
- }
131
- else if (totalRamGB < 16) {
132
- console.log(chalk.yellow(` ⚠ ${totalRamGB} GB RAM is below the recommended 16 GB.`));
133
- console.log(chalk.yellow(" Workers may run slowly or be killed by the OS under memory pressure."));
134
- }
135
- else {
136
- console.log(chalk.green(" ✓ System meets requirements"));
137
- }
138
- console.log();
139
- // ── Step 1: Docker ────────────────────────────────────────────────────────
140
- const dockerSpinner = ora("Checking Docker...").start();
141
- if (commandExists("docker")) {
142
- const version = getVersion("docker");
143
- // Verify Docker daemon is actually running (not just the CLI installed)
144
- try {
145
- const osType = execSync("docker info --format {{.OSType}}", {
146
- encoding: "utf-8",
147
- timeout: 15000,
148
- }).trim();
149
- // On Windows, verify Linux containers mode
150
- if (isWindows && osType === "windows") {
151
- dockerSpinner.fail("Docker is running in Windows containers mode");
152
- console.log();
153
- console.log(chalk.yellow(" WorkerMill workers require Linux containers."));
154
- console.log(chalk.yellow(" Right-click the Docker Desktop icon in the system tray →"));
155
- console.log(chalk.yellow(" 'Switch to Linux containers...'"));
156
- console.log();
157
- console.log(" Then re-run: workermill-agent");
158
- process.exit(1);
159
- }
160
- }
161
- catch {
162
- dockerSpinner.fail("Docker is installed but the daemon is not running");
163
- console.log();
164
- console.log(chalk.yellow(" Start Docker Desktop, wait for it to fully initialize,"));
165
- console.log(chalk.yellow(" then re-run: workermill-agent"));
166
- process.exit(1);
167
- }
168
- dockerSpinner.succeed(`Docker ${chalk.dim(version ? `(${version})` : "")}`);
169
- }
170
- else {
171
- dockerSpinner.fail("Docker is not installed");
172
- console.log();
173
- console.log(chalk.yellow(" Docker Desktop is required but must be installed manually:"));
174
- console.log(` ${chalk.cyan("https://docs.docker.com/get-docker/")}`);
175
- console.log();
176
- console.log(" Install Docker, start it, then re-run: workermill-agent");
177
- process.exit(1);
178
- }
179
- // ── Step 2: Git for Windows (required by Claude Code on Windows) ────────
180
- if (isWindows) {
181
- const gitSpinner = ora("Checking Git for Windows...").start();
182
- const gitBashPath = findGitBash();
183
- if (gitBashPath) {
184
- gitSpinner.succeed(`Git for Windows ${chalk.dim(`(${gitBashPath})`)}`);
185
- // Set CLAUDE_CODE_GIT_BASH_PATH so Claude can find it
186
- process.env.CLAUDE_CODE_GIT_BASH_PATH = gitBashPath;
187
- }
188
- else {
189
- gitSpinner.fail("Git for Windows is not installed");
190
- console.log();
191
- console.log(chalk.yellow(" Claude Code on Windows requires Git for Windows (Git Bash):"));
192
- console.log(` ${chalk.cyan("https://git-scm.com/downloads/win")}`);
193
- console.log();
194
- console.log(" Install Git for Windows, then re-run: workermill-agent");
195
- process.exit(1);
196
- }
197
- }
198
- // ── Step 3: Claude CLI ────────────────────────────────────────────────────
199
- const claudeSpinner = ora("Checking Claude CLI...").start();
200
- let claudePath = findClaudePath();
201
- if (claudePath) {
202
- const version = getVersion(claudePath);
203
- claudeSpinner.succeed(`Claude CLI ${chalk.dim(version ? `(${version})` : "")}`);
204
- }
205
- else {
206
- claudeSpinner.warn("Claude CLI not found — installing...");
207
- console.log();
208
- const installed = installClaudeCli();
209
- if (installed) {
210
- claudePath = findClaudePath();
211
- }
212
- if (claudePath) {
213
- const version = getVersion(claudePath);
214
- console.log();
215
- console.log(chalk.green(` ✓ Claude CLI installed ${chalk.dim(version ? `(${version})` : "")}`));
216
- }
217
- else {
218
- console.log();
219
- console.log(chalk.red(" Claude CLI was installed but could not be found."));
220
- console.log();
221
- if (isWindows) {
222
- console.log(chalk.yellow(" Winget updated your PATH but this shell doesn't have it yet."));
223
- console.log(chalk.yellow(" Close this terminal, open a new one, and re-run: workermill-agent"));
224
- }
225
- else {
226
- console.log(" Try manually:");
227
- console.log(chalk.cyan(" curl -fsSL https://claude.ai/install.sh | bash"));
228
- console.log(" Then re-run: workermill-agent");
229
- }
230
- process.exit(1);
231
- }
232
- }
233
- // ── Step 4: Claude auth ───────────────────────────────────────────────────
234
- const authSpinner = ora("Checking Claude authentication...").start();
235
- const credsPath = join(homedir(), ".claude", ".credentials.json");
236
- if (existsSync(credsPath)) {
237
- authSpinner.succeed("Claude authenticated");
238
- }
239
- else {
240
- authSpinner.warn("Not authenticated — launching Claude...");
241
- console.log();
242
- console.log(chalk.dim(" Claude will open and prompt you to authenticate."));
243
- console.log(chalk.dim(" Sign in with your Claude Max account, then exit Claude (Ctrl+C)."));
244
- console.log();
245
- // Pass Git Bash path on Windows so Claude can find it
246
- const env = { ...process.env };
247
- if (isWindows) {
248
- const gitBash = findGitBash();
249
- if (gitBash)
250
- env.CLAUDE_CODE_GIT_BASH_PATH = gitBash;
251
- }
252
- spawnSync(claudePath, [], {
253
- stdio: "inherit",
254
- timeout: 300_000,
255
- env,
256
- });
257
- if (existsSync(credsPath)) {
258
- console.log();
259
- console.log(chalk.green(" ✓ Claude authenticated"));
260
- }
261
- else {
262
- console.log();
263
- console.log(chalk.red(" Authentication failed or was cancelled."));
264
- console.log(` Try manually: open a new terminal and run 'claude'`);
265
- console.log(" Then re-run: workermill-agent");
266
- process.exit(1);
267
- }
268
- }
269
- // ── Step 5: Node.js ──────────────────────────────────────────────────────
270
- console.log(chalk.green(" ✓") + ` Node.js ${chalk.dim(`(${process.version})`)}`);
271
- console.log();
272
- // ── Step 6: Configuration prompts ─────────────────────────────────────────
273
- console.log(chalk.bold("Configuration"));
274
- console.log();
275
- const { apiUrl } = await inquirer.prompt([
276
- {
277
- type: "input",
278
- name: "apiUrl",
279
- message: "WorkerMill API URL:",
280
- default: "https://workermill.com",
281
- validate: (v) => v.startsWith("http://") || v.startsWith("https://") ? true : "Must be a valid URL",
282
- },
283
- ]);
284
- const { apiKey } = await inquirer.prompt([
285
- {
286
- type: "password",
287
- name: "apiKey",
288
- message: "API Key (from Settings > Integrations):",
289
- mask: "*",
290
- validate: (v) => (v.length > 0 ? true : "API key is required"),
291
- },
292
- ]);
293
- // Validate API key
294
- const validateSpinner = ora("Validating API key...").start();
295
- let scmProvider = "github";
296
- try {
297
- const resp = await axios.get(`${apiUrl.replace(/\/$/, "")}/api/agent/config`, {
298
- headers: { "x-api-key": apiKey },
299
- timeout: 15000,
300
- });
301
- scmProvider = resp.data.scmProvider || "github";
302
- validateSpinner.succeed(`Connected! SCM provider: ${scmProvider}`);
303
- }
304
- catch (error) {
305
- const err = error;
306
- if (err.response?.status === 401) {
307
- validateSpinner.fail("Invalid API key. Check Settings > Integrations on the dashboard.");
308
- }
309
- else {
310
- validateSpinner.fail("Failed to connect to WorkerMill API. Check the URL and try again.");
311
- }
312
- process.exit(1);
313
- }
314
- // SCM tokens come from org Settings > Integrations (no local prompt needed)
315
- console.log(chalk.dim(" SCM tokens are managed via Settings > Integrations on the dashboard."));
316
- console.log();
317
- const { agentId } = await inquirer.prompt([
318
- {
319
- type: "input",
320
- name: "agentId",
321
- message: "Agent name:",
322
- default: `agent-${hostname()}`,
323
- },
324
- ]);
325
- // ── Step 7: AWS CLI + ECR auth + pull worker image ───────────────────────
326
- console.log();
327
- const PRIVATE_ECR_REGISTRY = "593971626975.dkr.ecr.us-east-1.amazonaws.com";
328
- const workerImage = `${PRIVATE_ECR_REGISTRY}/workermill-dev/worker:latest`;
329
- // Check AWS CLI
330
- const awsSpinner = ora("Checking AWS CLI...").start();
331
- if (commandExists("aws")) {
332
- try {
333
- execSync("aws sts get-caller-identity", {
334
- stdio: "pipe",
335
- timeout: 15000,
336
- });
337
- awsSpinner.succeed("AWS CLI configured");
338
- }
339
- catch {
340
- awsSpinner.warn("AWS CLI found but credentials not configured");
341
- console.log();
342
- console.log(chalk.yellow(" Configure AWS credentials for private ECR image access:"));
343
- console.log(chalk.cyan(" aws configure"));
344
- console.log(chalk.dim(" Contact your WorkerMill admin for AWS access key / secret key."));
345
- console.log(chalk.dim(" Setup will continue — you can configure AWS later before starting."));
346
- }
347
- }
348
- else {
349
- awsSpinner.warn("AWS CLI not found");
350
- console.log();
351
- console.log(chalk.yellow(" AWS CLI is required for pulling worker images from private ECR."));
352
- console.log(chalk.cyan(" Install: https://docs.aws.amazon.com/cli/latest/userguide/getting-started-install.html"));
353
- console.log(chalk.dim(" Setup will continue — install AWS CLI before starting the agent."));
354
- }
355
- // Authenticate with ECR
356
- console.log();
357
- console.log(chalk.dim(` Authenticating with private ECR...`));
358
- let ecrAuthed = false;
359
- try {
360
- execSync(`aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${PRIVATE_ECR_REGISTRY}`, { stdio: "pipe", timeout: 30_000 });
361
- console.log(chalk.green(" ✓ ECR authenticated"));
362
- ecrAuthed = true;
363
- }
364
- catch {
365
- console.log(chalk.yellow(" ⚠ ECR authentication failed (AWS credentials may not be configured yet)"));
366
- console.log(chalk.dim(" Worker image pull will be skipped — authenticate later with:"));
367
- console.log(chalk.dim(` aws ecr get-login-password --region us-east-1 | docker login --username AWS --password-stdin ${PRIVATE_ECR_REGISTRY}`));
368
- }
369
- // Pull worker image (only if ECR auth succeeded)
370
- if (ecrAuthed) {
371
- console.log();
372
- console.log(chalk.dim(` Pulling worker image: ${workerImage}`));
373
- console.log(chalk.dim(" This may take a few minutes on first run (~1.1 GB)..."));
374
- console.log();
375
- const pullResult = spawnSync("docker", ["pull", workerImage], {
376
- stdio: "inherit",
377
- timeout: 600_000,
378
- });
379
- if (pullResult.status === 0) {
380
- console.log();
381
- console.log(chalk.green(" ✓ Worker image pulled"));
382
- }
383
- else {
384
- console.log();
385
- console.log(chalk.red(" ✗ Failed to pull worker image."));
386
- if (pullResult.error) {
387
- console.log(chalk.yellow(` Error: ${pullResult.error.message}`));
388
- }
389
- console.log(chalk.dim(" Setup will continue — you can pull the image later before starting."));
390
- }
391
- }
392
- // ── Step 8: Save config ───────────────────────────────────────────────────
393
- const fileConfig = {
394
- apiUrl: apiUrl.replace(/\/$/, ""),
395
- apiKey,
396
- agentId,
397
- maxWorkers: 1,
398
- pollIntervalMs: 5000,
399
- heartbeatIntervalMs: 30000,
400
- tokens: { github: "", bitbucket: "", gitlab: "" }, // SCM tokens come from org Settings
401
- workerImage,
402
- setupCompletedAt: new Date().toISOString(),
403
- };
404
- saveConfigToFile(fileConfig);
405
- console.log();
406
- console.log(chalk.green.bold(" Setup complete!"));
407
- console.log();
408
- console.log(` Config saved to: ${chalk.dim(getConfigFile())}`);
409
- console.log();
410
- console.log(` Start the agent with: ${chalk.cyan("workermill-agent start")}`);
411
- console.log();
412
- }
@@ -1,11 +0,0 @@
1
- /**
2
- * Start Command — Start the WorkerMill Remote Agent.
3
- *
4
- * Loads config from ~/.workermill/config.json, validates prerequisites,
5
- * registers with the cloud API, and starts polling.
6
- *
7
- * Supports --detach mode for running as a daemon.
8
- */
9
- export declare function startCommand(options: {
10
- detach?: boolean;
11
- }): Promise<void>;
@@ -1,152 +0,0 @@
1
- /**
2
- * Start Command — Start the WorkerMill Remote Agent.
3
- *
4
- * Loads config from ~/.workermill/config.json, validates prerequisites,
5
- * registers with the cloud API, and starts polling.
6
- *
7
- * Supports --detach mode for running as a daemon.
8
- */
9
- import chalk from "chalk";
10
- import { totalmem } from "os";
11
- import { spawn } from "child_process";
12
- import { writeFileSync, existsSync, unlinkSync, openSync, createWriteStream } from "fs";
13
- import { AGENT_VERSION } from "../version.js";
14
- import { loadConfigFromFile, checkPrerequisites, getSystemInfo, getPidFile, getLogFile, getConfigFile, } from "../config.js";
15
- import { startAgent } from "../index.js";
16
- export async function startCommand(options) {
17
- // Check config exists
18
- if (!existsSync(getConfigFile())) {
19
- console.log(chalk.red("No configuration found."));
20
- console.log(`Run ${chalk.cyan("workermill-agent setup")} first.`);
21
- process.exit(1);
22
- }
23
- const config = loadConfigFromFile();
24
- // Validate prerequisites
25
- const prereqs = checkPrerequisites(config.workerImage);
26
- const failing = prereqs.filter((p) => !p.ok);
27
- // Auto-pull worker image if it's the only missing prereq
28
- const imageMissing = failing.find((p) => p.name === "Worker image");
29
- // Claude CLI and auth are soft prerequisites — only needed for Anthropic provider.
30
- // Non-Anthropic orgs can plan+execute without Claude CLI.
31
- const softPrereqs = new Set(["Claude CLI", "Claude auth"]);
32
- const hardFailing = failing.filter((p) => p.name !== "Worker image" && !softPrereqs.has(p.name));
33
- const softFailing = failing.filter((p) => softPrereqs.has(p.name));
34
- if (hardFailing.length > 0) {
35
- console.log(chalk.red("Prerequisites check failed:"));
36
- for (const p of hardFailing) {
37
- console.log(chalk.red(` ✗ ${p.name}: ${p.detail}`));
38
- }
39
- process.exit(1);
40
- }
41
- if (softFailing.length > 0) {
42
- for (const p of softFailing) {
43
- console.log(chalk.yellow(` ⚠ ${p.name}: ${p.detail} (required for Anthropic provider)`));
44
- }
45
- }
46
- if (imageMissing) {
47
- console.log(chalk.yellow(` Worker image not found locally. Pulling ${config.workerImage}...`));
48
- const { spawnSync } = await import("child_process");
49
- const pull = spawnSync("docker", ["pull", config.workerImage], {
50
- stdio: "inherit",
51
- timeout: 600_000,
52
- });
53
- if (pull.status !== 0) {
54
- console.log(chalk.red(` Failed to pull worker image.`));
55
- process.exit(1);
56
- }
57
- console.log(chalk.green(` ✓ Worker image pulled`));
58
- }
59
- if (options.detach) {
60
- const logFile = getLogFile();
61
- const pidFile = getPidFile();
62
- console.log(chalk.dim(`Starting agent in background...`));
63
- console.log(chalk.dim(` Logs: ${logFile}`));
64
- console.log(chalk.dim(` PID: ${pidFile}`));
65
- // Spawn the CLI with "start" (no --detach) as a detached child, redirecting output to log file
66
- const logFd = openSync(logFile, "a");
67
- const child = spawn("workermill-agent", ["start"], {
68
- detached: true,
69
- stdio: ["ignore", logFd, logFd],
70
- shell: true, // Required on Windows to find .cmd wrappers
71
- });
72
- if (child.pid) {
73
- writeFileSync(pidFile, String(child.pid), "utf-8");
74
- child.unref();
75
- console.log(chalk.green(`Agent started (PID: ${child.pid})`));
76
- console.log(`Check status with: ${chalk.cyan("workermill-agent status")}`);
77
- }
78
- else {
79
- console.log(chalk.red("Failed to start agent in background."));
80
- process.exit(1);
81
- }
82
- return;
83
- }
84
- // Foreground mode — tee stdout/stderr to log file so `workermill-agent logs` works
85
- const logFile = getLogFile();
86
- const logStream = createWriteStream(logFile, { flags: "a" });
87
- const origStdoutWrite = process.stdout.write.bind(process.stdout);
88
- const origStderrWrite = process.stderr.write.bind(process.stderr);
89
- process.stdout.write = (chunk, ...args) => {
90
- logStream.write(chunk);
91
- return origStdoutWrite(chunk, ...args);
92
- };
93
- process.stderr.write = (chunk, ...args) => {
94
- logStream.write(chunk);
95
- return origStderrWrite(chunk, ...args);
96
- };
97
- console.log();
98
- console.log(chalk.bold.cyan(" WorkerMill Remote Agent"));
99
- console.log(chalk.dim(" ─────────────────────────────────────"));
100
- console.log();
101
- // RAM check
102
- const totalRamGB = Math.round(totalmem() / (1024 * 1024 * 1024));
103
- if (totalRamGB < 8) {
104
- console.log(chalk.red(` ✗ Insufficient RAM: ${totalRamGB} GB (minimum 8 GB, recommended 16 GB)`));
105
- process.exit(1);
106
- }
107
- else if (totalRamGB < 16) {
108
- console.log(chalk.yellow(` ⚠ RAM: ${totalRamGB} GB (below recommended 16 GB — workers may be slow)`));
109
- }
110
- // Register with system info
111
- const sysInfo = getSystemInfo();
112
- console.log(chalk.dim(` Agent: ${config.agentId}`));
113
- console.log(chalk.dim(` Version: ${AGENT_VERSION}`));
114
- console.log(chalk.dim(` Image: ${config.workerImage}`));
115
- console.log();
116
- try {
117
- const cleanup = await startAgent(config);
118
- // Write PID file for status command
119
- writeFileSync(getPidFile(), String(process.pid), "utf-8");
120
- // Graceful shutdown with force-exit safety net
121
- let shuttingDown = false;
122
- const shutdown = async () => {
123
- if (shuttingDown) {
124
- // Double Ctrl+C → force exit immediately
125
- console.log(chalk.red("\n Force exit."));
126
- process.exit(1);
127
- }
128
- shuttingDown = true;
129
- // Force exit after 10s if cleanup hangs
130
- const forceTimer = setTimeout(() => {
131
- console.log(chalk.red("\n Cleanup timed out. Force exit."));
132
- process.exit(1);
133
- }, 10_000);
134
- forceTimer.unref(); // Don't keep process alive just for this timer
135
- await cleanup();
136
- clearTimeout(forceTimer);
137
- try {
138
- unlinkSync(getPidFile());
139
- }
140
- catch {
141
- /* ignore */
142
- }
143
- process.exit(0);
144
- };
145
- process.on("SIGINT", shutdown);
146
- process.on("SIGTERM", shutdown);
147
- }
148
- catch (error) {
149
- console.log(chalk.red(`Failed to start: ${error instanceof Error ? error.message : String(error)}`));
150
- process.exit(1);
151
- }
152
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Status Command — Show the current state of the WorkerMill Remote Agent.
3
- *
4
- * Displays: running/stopped, agent ID, active containers, API connectivity.
5
- */
6
- export declare function statusCommand(): Promise<void>;
@@ -1,86 +0,0 @@
1
- /**
2
- * Status Command — Show the current state of the WorkerMill Remote Agent.
3
- *
4
- * Displays: running/stopped, agent ID, active containers, API connectivity.
5
- */
6
- import chalk from "chalk";
7
- import { existsSync, readFileSync } from "fs";
8
- import { execSync } from "child_process";
9
- import axios from "axios";
10
- import { getPidFile, getConfigFile, loadConfigFromFile } from "../config.js";
11
- export async function statusCommand() {
12
- console.log();
13
- console.log(chalk.bold("WorkerMill Remote Agent Status"));
14
- console.log(chalk.dim("─────────────────────────────────────"));
15
- console.log();
16
- // Check config
17
- if (!existsSync(getConfigFile())) {
18
- console.log(chalk.yellow(" Not configured. Run 'workermill-agent setup' first."));
19
- return;
20
- }
21
- const config = loadConfigFromFile();
22
- // Agent process status
23
- const pidFile = getPidFile();
24
- let isRunning = false;
25
- let pid = null;
26
- if (existsSync(pidFile)) {
27
- const pidStr = readFileSync(pidFile, "utf-8").trim();
28
- pid = parseInt(pidStr, 10);
29
- if (!isNaN(pid)) {
30
- try {
31
- process.kill(pid, 0);
32
- isRunning = true;
33
- }
34
- catch {
35
- isRunning = false;
36
- }
37
- }
38
- }
39
- const statusIcon = isRunning ? chalk.green("● online") : chalk.red("● offline");
40
- console.log(` Status: ${statusIcon}${pid ? chalk.dim(` (PID ${pid})`) : ""}`);
41
- console.log(` Agent ID: ${config.agentId}`);
42
- console.log(` API URL: ${config.apiUrl}`);
43
- console.log(` Max workers: ${config.maxWorkers}`);
44
- console.log(` Image: ${config.workerImage}`);
45
- // Active containers
46
- console.log();
47
- console.log(chalk.bold(" Active Containers"));
48
- try {
49
- const output = execSync('docker ps --filter "name=workermill-" --format "{{.Names}}\t{{.Status}}\t{{.RunningFor}}"', { encoding: "utf-8", timeout: 10000 }).trim();
50
- if (output) {
51
- const lines = output.split("\n");
52
- console.log(chalk.dim(` Found ${lines.length} container(s):`));
53
- for (const line of lines) {
54
- const [name, status, running] = line.split("\t");
55
- console.log(` ${chalk.cyan(name)} ${status} ${chalk.dim(running || "")}`);
56
- }
57
- }
58
- else {
59
- console.log(chalk.dim(" No active containers"));
60
- }
61
- }
62
- catch {
63
- console.log(chalk.dim(" Could not query Docker"));
64
- }
65
- // API connectivity
66
- console.log();
67
- console.log(chalk.bold(" API Connectivity"));
68
- try {
69
- const resp = await axios.get(`${config.apiUrl}/api/agent/config`, {
70
- headers: { "x-api-key": config.apiKey },
71
- timeout: 10000,
72
- });
73
- console.log(` ${chalk.green("✓")} Connected to ${config.apiUrl}`);
74
- console.log(chalk.dim(` SCM: ${resp.data.scmProvider}, Model: ${resp.data.defaultWorkerModel}`));
75
- }
76
- catch (error) {
77
- const err = error;
78
- if (err.response?.status === 401) {
79
- console.log(` ${chalk.red("✗")} Authentication failed (invalid API key)`);
80
- }
81
- else {
82
- console.log(` ${chalk.red("✗")} Cannot reach ${config.apiUrl}`);
83
- }
84
- }
85
- console.log();
86
- }
@@ -1,6 +0,0 @@
1
- /**
2
- * Stop Command — Stop a running WorkerMill Remote Agent daemon.
3
- *
4
- * Reads PID from ~/.workermill/agent.pid, sends SIGTERM, waits for exit.
5
- */
6
- export declare function stopCommand(): Promise<void>;