@workermill/agent 0.8.8 → 0.8.10

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,61 +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
- import chalk from "chalk";
7
- import { existsSync, readFileSync, unlinkSync } from "fs";
8
- import { getPidFile } from "../config.js";
9
- export async function stopCommand() {
10
- const pidFile = getPidFile();
11
- if (!existsSync(pidFile)) {
12
- console.log(chalk.yellow("No running agent found (no PID file)."));
13
- console.log(chalk.dim(`Expected PID file at: ${pidFile}`));
14
- return;
15
- }
16
- const pidStr = readFileSync(pidFile, "utf-8").trim();
17
- const pid = parseInt(pidStr, 10);
18
- if (isNaN(pid)) {
19
- console.log(chalk.red(`Invalid PID in ${pidFile}: ${pidStr}`));
20
- unlinkSync(pidFile);
21
- return;
22
- }
23
- // Check if process is running
24
- try {
25
- process.kill(pid, 0); // Signal 0 = check existence
26
- }
27
- catch {
28
- console.log(chalk.yellow(`Agent (PID ${pid}) is not running. Cleaning up PID file.`));
29
- unlinkSync(pidFile);
30
- return;
31
- }
32
- // Send SIGTERM
33
- console.log(chalk.dim(`Stopping agent (PID ${pid})...`));
34
- try {
35
- process.kill(pid, "SIGTERM");
36
- }
37
- catch (error) {
38
- console.log(chalk.red(`Failed to stop agent: ${error instanceof Error ? error.message : String(error)}`));
39
- return;
40
- }
41
- // Wait for process to exit (up to 15 seconds)
42
- const start = Date.now();
43
- while (Date.now() - start < 15000) {
44
- try {
45
- process.kill(pid, 0);
46
- await new Promise((r) => setTimeout(r, 500));
47
- }
48
- catch {
49
- // Process has exited
50
- break;
51
- }
52
- }
53
- // Clean up PID file
54
- try {
55
- unlinkSync(pidFile);
56
- }
57
- catch {
58
- /* ignore */
59
- }
60
- console.log(chalk.green("Agent stopped."));
61
- }
@@ -1 +0,0 @@
1
- export declare function updateCommand(): Promise<void>;
@@ -1,20 +0,0 @@
1
- import chalk from "chalk";
2
- import { AGENT_VERSION } from "../version.js";
3
- import { selfUpdate } from "../updater.js";
4
- export async function updateCommand() {
5
- console.log();
6
- console.log(chalk.bold.cyan(" WorkerMill Agent Update"));
7
- console.log(chalk.dim(" ─────────────────────────────────────"));
8
- console.log(` ${chalk.dim("Current version:")} ${AGENT_VERSION}`);
9
- console.log();
10
- const success = await selfUpdate();
11
- if (success) {
12
- console.log();
13
- console.log(chalk.green(" Update complete. If the agent is running, restart it to use the new version."));
14
- }
15
- else {
16
- console.log();
17
- console.error(chalk.red(" Update failed. Try running manually: npm install -g @workermill/agent@latest"));
18
- process.exitCode = 1;
19
- }
20
- }
package/dist/config.d.ts DELETED
@@ -1,77 +0,0 @@
1
- /**
2
- * Remote Agent Configuration
3
- *
4
- * Supports two modes:
5
- * 1. File-based config (~/.workermill/config.json) — for npm-installed CLI
6
- * 2. Environment variable config (.env.remote) — for bin/remote-agent backward compat
7
- */
8
- export interface AgentConfig {
9
- apiUrl: string;
10
- apiKey: string;
11
- agentId: string;
12
- maxWorkers: number;
13
- pollIntervalMs: number;
14
- heartbeatIntervalMs: number;
15
- githubToken: string;
16
- bitbucketToken: string;
17
- gitlabToken: string;
18
- workerImage: string;
19
- }
20
- export interface FileConfig {
21
- apiUrl: string;
22
- apiKey: string;
23
- agentId: string;
24
- maxWorkers: number;
25
- pollIntervalMs: number;
26
- heartbeatIntervalMs: number;
27
- tokens: {
28
- github: string;
29
- bitbucket: string;
30
- gitlab: string;
31
- };
32
- workerImage: string;
33
- setupCompletedAt: string;
34
- }
35
- export declare function getConfigDir(): string;
36
- export declare function getConfigFile(): string;
37
- export declare function getPidFile(): string;
38
- export declare function getLogFile(): string;
39
- /**
40
- * Load config from ~/.workermill/config.json (CLI mode).
41
- */
42
- export declare function loadConfigFromFile(): AgentConfig;
43
- /**
44
- * Save config to ~/.workermill/config.json with restricted permissions.
45
- */
46
- export declare function saveConfigToFile(fc: FileConfig): void;
47
- /**
48
- * Load config from environment variables (backward compat with bin/remote-agent).
49
- */
50
- export declare function loadConfig(): AgentConfig;
51
- /**
52
- * Find claude binary. Checks PATH, then known install locations.
53
- */
54
- export declare function findClaudePath(): string | null;
55
- export interface PrerequisiteResult {
56
- name: string;
57
- ok: boolean;
58
- detail?: string;
59
- }
60
- /**
61
- * Check all prerequisites and return results (non-exiting version for setup wizard).
62
- */
63
- export declare function checkPrerequisites(workerImage?: string): PrerequisiteResult[];
64
- /**
65
- * Validate prerequisites (exits on failure — backward compat).
66
- */
67
- export declare function validatePrerequisites(): void;
68
- /**
69
- * Get system info for agent registration.
70
- */
71
- export declare function getSystemInfo(): {
72
- hostname: string;
73
- platform: string;
74
- nodeVersion: string;
75
- dockerVersion: string;
76
- claudeVersion: string;
77
- };
package/dist/config.js DELETED
@@ -1,286 +0,0 @@
1
- /**
2
- * Remote Agent Configuration
3
- *
4
- * Supports two modes:
5
- * 1. File-based config (~/.workermill/config.json) — for npm-installed CLI
6
- * 2. Environment variable config (.env.remote) — for bin/remote-agent backward compat
7
- */
8
- import { existsSync, readFileSync, mkdirSync, writeFileSync, chmodSync } from "fs";
9
- import { execSync } from "child_process";
10
- import { hostname, homedir } from "os";
11
- import { join } from "path";
12
- const CONFIG_DIR = join(homedir(), ".workermill");
13
- const CONFIG_FILE = join(CONFIG_DIR, "config.json");
14
- const PID_FILE = join(CONFIG_DIR, "agent.pid");
15
- const LOG_FILE = join(CONFIG_DIR, "agent.log");
16
- export function getConfigDir() {
17
- return CONFIG_DIR;
18
- }
19
- export function getConfigFile() {
20
- return CONFIG_FILE;
21
- }
22
- export function getPidFile() {
23
- return PID_FILE;
24
- }
25
- export function getLogFile() {
26
- return LOG_FILE;
27
- }
28
- /**
29
- * Load config from ~/.workermill/config.json (CLI mode).
30
- */
31
- export function loadConfigFromFile() {
32
- if (!existsSync(CONFIG_FILE)) {
33
- console.error("No config found. Run 'workermill-agent setup' first.");
34
- process.exit(1);
35
- }
36
- let raw;
37
- try {
38
- raw = readFileSync(CONFIG_FILE, "utf-8");
39
- }
40
- catch {
41
- console.error("Failed to read config file:", CONFIG_FILE);
42
- process.exit(1);
43
- }
44
- let fc;
45
- try {
46
- fc = JSON.parse(raw);
47
- }
48
- catch {
49
- console.error("Config file is corrupted. Re-run 'workermill-agent setup'.");
50
- process.exit(1);
51
- }
52
- if (!fc.apiUrl || !fc.apiKey) {
53
- console.error("Config file is missing required fields (apiUrl, apiKey). Re-run 'workermill-agent setup'.");
54
- process.exit(1);
55
- }
56
- // Migrate any stale image URLs to current default (private ECR)
57
- const defaultImage = "593971626975.dkr.ecr.us-east-1.amazonaws.com/workermill-dev/worker:latest";
58
- let workerImage = fc.workerImage || defaultImage;
59
- if (workerImage.includes("jarod1/") ||
60
- workerImage.includes("public.ecr.aws/") ||
61
- !workerImage.includes("workermill-worker") && !workerImage.includes("workermill-dev/worker")) {
62
- workerImage = defaultImage;
63
- fc.workerImage = workerImage;
64
- try {
65
- writeFileSync(CONFIG_FILE, JSON.stringify(fc, null, 2), "utf-8");
66
- }
67
- catch { /* best effort */ }
68
- }
69
- return {
70
- apiUrl: fc.apiUrl,
71
- apiKey: fc.apiKey,
72
- agentId: fc.agentId,
73
- maxWorkers: fc.maxWorkers || 4,
74
- pollIntervalMs: fc.pollIntervalMs || 5000,
75
- heartbeatIntervalMs: fc.heartbeatIntervalMs || 30000,
76
- githubToken: fc.tokens?.github || "",
77
- bitbucketToken: fc.tokens?.bitbucket || "",
78
- gitlabToken: fc.tokens?.gitlab || "",
79
- workerImage,
80
- };
81
- }
82
- /**
83
- * Save config to ~/.workermill/config.json with restricted permissions.
84
- */
85
- export function saveConfigToFile(fc) {
86
- if (!existsSync(CONFIG_DIR)) {
87
- mkdirSync(CONFIG_DIR, { recursive: true });
88
- }
89
- writeFileSync(CONFIG_FILE, JSON.stringify(fc, null, 2), "utf-8");
90
- // Restrict permissions (owner-only read/write)
91
- try {
92
- chmodSync(CONFIG_FILE, 0o600);
93
- }
94
- catch {
95
- // chmod may not work on Windows, that's OK
96
- }
97
- }
98
- /**
99
- * Load config from environment variables (backward compat with bin/remote-agent).
100
- */
101
- export function loadConfig() {
102
- const apiUrl = process.env.WORKERMILL_API_URL;
103
- const apiKey = process.env.WORKERMILL_API_KEY;
104
- if (!apiUrl) {
105
- console.error("WORKERMILL_API_URL is required in .env.remote");
106
- process.exit(1);
107
- }
108
- if (!apiKey) {
109
- console.error("WORKERMILL_API_KEY is required in .env.remote");
110
- console.error("Get your API key from Settings > Integrations on the WorkerMill dashboard.");
111
- process.exit(1);
112
- }
113
- return {
114
- apiUrl: apiUrl.replace(/\/$/, ""), // Strip trailing slash
115
- apiKey,
116
- agentId: process.env.AGENT_ID || `agent-${hostname()}`,
117
- maxWorkers: parseInt(process.env.MAX_WORKERS || "4", 10),
118
- pollIntervalMs: parseInt(process.env.POLL_INTERVAL_MS || "5000", 10),
119
- heartbeatIntervalMs: parseInt(process.env.HEARTBEAT_INTERVAL_MS || "30000", 10),
120
- githubToken: process.env.GITHUB_TOKEN || "",
121
- bitbucketToken: process.env.BITBUCKET_TOKEN || "",
122
- gitlabToken: process.env.GITLAB_TOKEN || "",
123
- workerImage: process.env.WORKER_IMAGE || "workermill-worker:local",
124
- };
125
- }
126
- /**
127
- * Find claude binary. Checks PATH, then known install locations.
128
- */
129
- export function findClaudePath() {
130
- const isWin = process.platform === "win32";
131
- const which = isWin ? "where" : "which";
132
- // Check PATH first
133
- try {
134
- execSync(`${which} claude`, { stdio: "ignore", timeout: 10000 });
135
- return "claude";
136
- }
137
- catch { /* not in PATH */ }
138
- const candidates = [];
139
- if (isWin) {
140
- candidates.push(join(process.env.ProgramFiles || "C:\\Program Files", "ClaudeCode", "claude.exe"), join(process.env.LOCALAPPDATA || "", "Programs", "ClaudeCode", "claude.exe"), join(homedir(), "AppData", "Local", "Programs", "ClaudeCode", "claude.exe"), join(homedir(), ".local", "bin", "claude.exe"));
141
- }
142
- else {
143
- candidates.push(join(homedir(), ".local", "bin", "claude"), "/opt/homebrew/bin/claude", "/usr/local/bin/claude");
144
- }
145
- for (const candidate of candidates) {
146
- if (candidate && existsSync(candidate))
147
- return candidate;
148
- }
149
- return null;
150
- }
151
- /**
152
- * Check all prerequisites and return results (non-exiting version for setup wizard).
153
- */
154
- export function checkPrerequisites(workerImage) {
155
- const results = [];
156
- const image = workerImage || "593971626975.dkr.ecr.us-east-1.amazonaws.com/workermill-dev/worker:latest";
157
- // Docker
158
- try {
159
- const version = execSync("docker version --format {{.Server.Version}}", {
160
- encoding: "utf-8",
161
- timeout: 10000,
162
- }).trim();
163
- results.push({ name: "Docker", ok: true, detail: version });
164
- }
165
- catch {
166
- results.push({ name: "Docker", ok: false, detail: "Not running or not installed" });
167
- }
168
- // Claude CLI (search known install locations, not just PATH)
169
- const claudePath = findClaudePath();
170
- if (claudePath) {
171
- try {
172
- const version = execSync(`"${claudePath}" --version`, { encoding: "utf-8", timeout: 10000 }).trim();
173
- results.push({ name: "Claude CLI", ok: true, detail: version });
174
- }
175
- catch {
176
- results.push({ name: "Claude CLI", ok: true, detail: claudePath });
177
- }
178
- }
179
- else {
180
- results.push({ name: "Claude CLI", ok: false, detail: "Not installed" });
181
- }
182
- // Claude credentials
183
- const home = homedir();
184
- const credsPath = join(home, ".claude", ".credentials.json");
185
- if (existsSync(credsPath)) {
186
- results.push({ name: "Claude auth", ok: true, detail: "Credentials found" });
187
- }
188
- else {
189
- results.push({ name: "Claude auth", ok: false, detail: "Run 'claude' and complete sign-in" });
190
- }
191
- // Node.js version
192
- const nodeVersion = process.version;
193
- const major = parseInt(nodeVersion.slice(1).split(".")[0], 10);
194
- if (major >= 20) {
195
- results.push({ name: "Node.js", ok: true, detail: nodeVersion });
196
- }
197
- else {
198
- results.push({ name: "Node.js", ok: false, detail: `${nodeVersion} (need >= 20)` });
199
- }
200
- // Worker image
201
- try {
202
- execSync(`docker image inspect ${image}`, { stdio: "ignore", timeout: 10000 });
203
- results.push({ name: "Worker image", ok: true, detail: image });
204
- }
205
- catch {
206
- results.push({ name: "Worker image", ok: false, detail: `'${image}' not found` });
207
- }
208
- return results;
209
- }
210
- /**
211
- * Validate prerequisites (exits on failure — backward compat).
212
- */
213
- export function validatePrerequisites() {
214
- // Check Docker
215
- try {
216
- execSync("docker version", { stdio: "ignore" });
217
- }
218
- catch {
219
- console.error("Docker is not available. Please install Docker and ensure it's running.");
220
- process.exit(1);
221
- }
222
- // Check worker image (use env-configured image or default)
223
- const image = process.env.WORKER_IMAGE || "workermill-worker:local";
224
- try {
225
- execSync(`docker image inspect ${image}`, { stdio: "ignore" });
226
- }
227
- catch {
228
- console.error(`Worker image '${image}' not found.`);
229
- if (image === "workermill-worker:local") {
230
- console.error("Build it with: ./bin/local-workermill build-worker");
231
- }
232
- else {
233
- console.error(`Pull it with: docker pull ${image}`);
234
- }
235
- process.exit(1);
236
- }
237
- // Check Claude CLI
238
- if (!findClaudePath()) {
239
- console.error("Claude CLI is not installed.");
240
- console.error("Install it: curl -fsSL https://claude.ai/install.sh | bash");
241
- process.exit(1);
242
- }
243
- // Check Claude credentials
244
- const home = homedir();
245
- const credsPath = join(home, ".claude", ".credentials.json");
246
- if (!existsSync(credsPath)) {
247
- console.error("Claude credentials not found.");
248
- console.error("Run 'claude' and complete the sign-in flow to authenticate.");
249
- process.exit(1);
250
- }
251
- }
252
- /**
253
- * Get system info for agent registration.
254
- */
255
- export function getSystemInfo() {
256
- let dockerVersion = "unknown";
257
- try {
258
- dockerVersion = execSync("docker version --format {{.Server.Version}}", {
259
- encoding: "utf-8",
260
- timeout: 10000,
261
- }).trim();
262
- }
263
- catch {
264
- /* ignore */
265
- }
266
- let claudeVersion = "unknown";
267
- const claudeBin = findClaudePath();
268
- if (claudeBin) {
269
- try {
270
- claudeVersion = execSync(`"${claudeBin}" --version`, {
271
- encoding: "utf-8",
272
- timeout: 10000,
273
- }).trim();
274
- }
275
- catch {
276
- /* ignore */
277
- }
278
- }
279
- return {
280
- hostname: hostname(),
281
- platform: process.platform,
282
- nodeVersion: process.version,
283
- dockerVersion,
284
- claudeVersion,
285
- };
286
- }
package/dist/index.d.ts DELETED
@@ -1,14 +0,0 @@
1
- /**
2
- * WorkerMill Remote Agent
3
- *
4
- * Importable module for starting the agent programmatically.
5
- * Can also be run directly via `bin/remote-agent` (backward compat with dotenv).
6
- */
7
- import type { AgentConfig } from "./config.js";
8
- export { loadConfig, loadConfigFromFile, validatePrerequisites, getSystemInfo, findClaudePath } from "./config.js";
9
- export type { AgentConfig } from "./config.js";
10
- /**
11
- * Start the remote agent with the given config.
12
- * Returns a cleanup function to stop the agent.
13
- */
14
- export declare function startAgent(config: AgentConfig): Promise<() => Promise<void>>;
@@ -1,104 +0,0 @@
1
- /**
2
- * Plan Validator for Remote Agent
3
- *
4
- * Validates execution plans locally before posting to the cloud API.
5
- * Implements the same guardrails as the server-side planning pipeline:
6
- * 1. File cap: max 5 targetFiles per story (prevents scope explosion)
7
- * 2. Critic validation: LLM scores the plan, rejects below threshold
8
- *
9
- * This ensures remote agent plans get the same quality gates as cloud plans,
10
- * even though the planning prompt runs locally via Claude CLI.
11
- */
12
- import { type AIProvider } from "./providers.js";
13
- export interface PlannedStory {
14
- id: string;
15
- title: string;
16
- description: string;
17
- persona: string;
18
- priority: number;
19
- estimatedEffort: "small" | "medium" | "large";
20
- dependencies: string[];
21
- acceptanceCriteria?: string[];
22
- targetFiles?: string[];
23
- scope?: string;
24
- }
25
- export interface ExecutionPlan {
26
- summary: string;
27
- stories: PlannedStory[];
28
- risks: string[];
29
- assumptions: string[];
30
- }
31
- export interface CriticResult {
32
- approved: boolean;
33
- score: number;
34
- risks: string[];
35
- suggestions?: string[];
36
- storyFeedback?: Array<{
37
- storyId: string;
38
- feedback: string;
39
- suggestedChanges?: string[];
40
- }>;
41
- }
42
- declare const AUTO_APPROVAL_THRESHOLD = 85;
43
- /**
44
- * Parse execution plan JSON from raw Claude CLI output.
45
- * Mirrors server-side parseExecutionPlan() in planning-agent-local.ts.
46
- */
47
- export declare function parseExecutionPlan(output: string): ExecutionPlan;
48
- /**
49
- * Apply file cap to all stories. Truncates targetFiles > MAX_TARGET_FILES.
50
- * Returns details about truncated stories for logging.
51
- */
52
- export declare function applyFileCap(plan: ExecutionPlan): {
53
- truncatedCount: number;
54
- details: string[];
55
- };
56
- /**
57
- * Apply story cap to the plan. Truncates stories beyond maxStories.
58
- * Returns details about dropped stories for logging.
59
- */
60
- export declare function applyStoryCap(plan: ExecutionPlan, maxStories: number): {
61
- droppedCount: number;
62
- details: string[];
63
- };
64
- /**
65
- * Resolve file overlaps by assigning each shared file to exactly one story.
66
- * When multiple stories list the same targetFile, the first story keeps it
67
- * and it's removed from subsequent stories. This prevents parallel merge
68
- * conflicts during consolidation — same auto-fix pattern as applyFileCap.
69
- *
70
- * Returns details about resolved overlaps for logging.
71
- */
72
- export declare function resolveFileOverlaps(plan: ExecutionPlan): {
73
- resolvedCount: number;
74
- details: string[];
75
- };
76
- /**
77
- * Re-serialize plan as a JSON code block for posting to the API.
78
- * The server-side parseExecutionPlan() expects ```json ... ``` blocks.
79
- */
80
- export declare function serializePlan(plan: ExecutionPlan): string;
81
- /**
82
- * Build the critic prompt with PRD and plan substituted.
83
- */
84
- export declare function buildCriticPrompt(prd: string, plan: ExecutionPlan): string;
85
- /**
86
- * Parse critic JSON response from raw Claude CLI output.
87
- */
88
- export declare function parseCriticResponse(text: string): CriticResult;
89
- /**
90
- * Run the critic via Claude CLI (lightweight — no tools, just reasoning).
91
- * Returns the raw text output.
92
- */
93
- export declare function runCriticCli(claudePath: string, model: string, prompt: string, env: Record<string, string | undefined>, taskId?: string): Promise<string>;
94
- /**
95
- * Format critic feedback for appending to the planner prompt on re-run.
96
- */
97
- export declare function formatCriticFeedback(critic: CriticResult): string;
98
- /**
99
- * Run critic validation on a parsed plan.
100
- * Routes to Claude CLI (Anthropic) or HTTP API (other providers).
101
- * Returns the critic result, or null if critic fails (non-blocking).
102
- */
103
- export declare function runCriticValidation(claudePath: string, model: string, prd: string, plan: ExecutionPlan, env: Record<string, string | undefined>, taskLabel: string, provider?: AIProvider, providerApiKey?: string, taskId?: string): Promise<CriticResult | null>;
104
- export { AUTO_APPROVAL_THRESHOLD };