workermill 0.1.9 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -5
- package/dist/{chunk-3KIFXIBC.js → chunk-VC6VNVEY.js} +214 -468
- package/dist/index.js +1707 -690
- package/dist/{orchestrator-NMTZUS23.js → orchestrator-5I7BGPC7.js} +597 -162
- package/package.json +7 -1
- package/personas/planner.md +1 -1
- package/dist/chunk-2NTK7H4W.js +0 -10
- package/dist/chunk-LVCJZJJH.js +0 -29
- package/dist/terminal-ILMO7Z3P.js +0 -17
- package/personas/ml_engineer.md +0 -32
- /package/personas/{data_engineer.md → data_ml_engineer.md} +0 -0
- /package/personas/{reviewer.md → tech_lead.md} +0 -0
|
@@ -1,9 +1,16 @@
|
|
|
1
|
+
// node_modules/tsup/assets/esm_shims.js
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
var getFilename = () => fileURLToPath(import.meta.url);
|
|
5
|
+
var getDirname = () => path.dirname(getFilename());
|
|
6
|
+
var __dirname = /* @__PURE__ */ getDirname();
|
|
7
|
+
|
|
1
8
|
// src/config.js
|
|
2
9
|
import fs from "fs";
|
|
3
|
-
import
|
|
10
|
+
import path2 from "path";
|
|
4
11
|
import os from "os";
|
|
5
|
-
var CONFIG_DIR =
|
|
6
|
-
var CONFIG_FILE =
|
|
12
|
+
var CONFIG_DIR = path2.join(os.homedir(), ".workermill");
|
|
13
|
+
var CONFIG_FILE = path2.join(CONFIG_DIR, "cli.json");
|
|
7
14
|
function loadConfig() {
|
|
8
15
|
try {
|
|
9
16
|
if (!fs.existsSync(CONFIG_FILE))
|
|
@@ -30,7 +37,8 @@ function getProviderForPersona(config, persona) {
|
|
|
30
37
|
provider: providerName,
|
|
31
38
|
model: providerConfig.model,
|
|
32
39
|
apiKey: providerConfig.apiKey?.startsWith("{env:") ? process.env[providerConfig.apiKey.slice(5, -1)] || void 0 : providerConfig.apiKey,
|
|
33
|
-
host: providerConfig.host
|
|
40
|
+
host: providerConfig.host,
|
|
41
|
+
contextLength: providerConfig.contextLength
|
|
34
42
|
};
|
|
35
43
|
}
|
|
36
44
|
|
|
@@ -39,7 +47,18 @@ import { anthropic } from "@ai-sdk/anthropic";
|
|
|
39
47
|
import { openai } from "@ai-sdk/openai";
|
|
40
48
|
import { google } from "@ai-sdk/google";
|
|
41
49
|
import { createOllama } from "ollama-ai-provider-v2";
|
|
42
|
-
function
|
|
50
|
+
function buildOllamaOptions(provider, contextLength) {
|
|
51
|
+
if (provider !== "ollama" || !contextLength)
|
|
52
|
+
return {};
|
|
53
|
+
return {
|
|
54
|
+
providerOptions: {
|
|
55
|
+
ollama: {
|
|
56
|
+
num_ctx: contextLength
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
function createModel(provider, modelName, ollamaHost, ollamaContextLength) {
|
|
43
62
|
switch (provider) {
|
|
44
63
|
case "anthropic":
|
|
45
64
|
return anthropic(modelName);
|
|
@@ -61,7 +80,7 @@ function createModel(provider, modelName, ollamaHost) {
|
|
|
61
80
|
// ../packages/engine/src/tools/index.js
|
|
62
81
|
import { tool } from "ai";
|
|
63
82
|
import { z } from "zod";
|
|
64
|
-
import
|
|
83
|
+
import path10 from "path";
|
|
65
84
|
|
|
66
85
|
// ../packages/engine/src/tools/bash.js
|
|
67
86
|
import { spawn } from "child_process";
|
|
@@ -123,10 +142,7 @@ async function execute({ command, cwd, timeout = 12e4 }) {
|
|
|
123
142
|
let killed = false;
|
|
124
143
|
const child = spawn("/bin/bash", ["-c", command], {
|
|
125
144
|
cwd: cwd || process.cwd(),
|
|
126
|
-
env:
|
|
127
|
-
...process.env,
|
|
128
|
-
PATH: "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
129
|
-
},
|
|
145
|
+
env: process.env,
|
|
130
146
|
stdio: ["pipe", "pipe", "pipe"],
|
|
131
147
|
detached: true
|
|
132
148
|
});
|
|
@@ -206,11 +222,11 @@ async function execute({ command, cwd, timeout = 12e4 }) {
|
|
|
206
222
|
|
|
207
223
|
// ../packages/engine/src/tools/read-file.js
|
|
208
224
|
import fs2 from "fs";
|
|
209
|
-
import
|
|
225
|
+
import path3 from "path";
|
|
210
226
|
var description2 = "Read the contents of a file. Returns the file content as a string. Supports text files of any type.";
|
|
211
227
|
async function execute2({ path: filePath, encoding = "utf8", maxLines, startLine }) {
|
|
212
228
|
try {
|
|
213
|
-
const absolutePath =
|
|
229
|
+
const absolutePath = path3.isAbsolute(filePath) ? filePath : path3.resolve(process.cwd(), filePath);
|
|
214
230
|
if (!fs2.existsSync(absolutePath)) {
|
|
215
231
|
return {
|
|
216
232
|
success: false,
|
|
@@ -263,12 +279,12 @@ async function execute2({ path: filePath, encoding = "utf8", maxLines, startLine
|
|
|
263
279
|
|
|
264
280
|
// ../packages/engine/src/tools/write-file.js
|
|
265
281
|
import fs3 from "fs";
|
|
266
|
-
import
|
|
282
|
+
import path4 from "path";
|
|
267
283
|
var description3 = "Write content to a file. Creates the file if it does not exist, and creates any necessary parent directories. Overwrites existing content.";
|
|
268
284
|
async function execute3({ path: filePath, content, encoding = "utf8", append = false }) {
|
|
269
285
|
try {
|
|
270
|
-
const absolutePath =
|
|
271
|
-
const dirPath =
|
|
286
|
+
const absolutePath = path4.isAbsolute(filePath) ? filePath : path4.resolve(process.cwd(), filePath);
|
|
287
|
+
const dirPath = path4.dirname(absolutePath);
|
|
272
288
|
if (!fs3.existsSync(dirPath)) {
|
|
273
289
|
fs3.mkdirSync(dirPath, { recursive: true });
|
|
274
290
|
}
|
|
@@ -304,11 +320,11 @@ async function execute3({ path: filePath, content, encoding = "utf8", append = f
|
|
|
304
320
|
|
|
305
321
|
// ../packages/engine/src/tools/edit-file.js
|
|
306
322
|
import fs4 from "fs";
|
|
307
|
-
import
|
|
323
|
+
import path5 from "path";
|
|
308
324
|
var description4 = "Edit a file by finding and replacing text. The old_string must be unique in the file (or use replaceAll for multiple occurrences). Use this instead of write_file when making targeted changes to existing files.";
|
|
309
325
|
async function execute4({ path: filePath, old_string, new_string, replaceAll = false }) {
|
|
310
326
|
try {
|
|
311
|
-
const absolutePath =
|
|
327
|
+
const absolutePath = path5.isAbsolute(filePath) ? filePath : path5.resolve(process.cwd(), filePath);
|
|
312
328
|
if (!fs4.existsSync(absolutePath)) {
|
|
313
329
|
return {
|
|
314
330
|
success: false,
|
|
@@ -375,7 +391,7 @@ async function execute4({ path: filePath, old_string, new_string, replaceAll = f
|
|
|
375
391
|
|
|
376
392
|
// ../packages/engine/src/tools/glob.js
|
|
377
393
|
import fs5 from "fs";
|
|
378
|
-
import
|
|
394
|
+
import path6 from "path";
|
|
379
395
|
function globToRegex(pattern) {
|
|
380
396
|
let regex = "";
|
|
381
397
|
let i = 0;
|
|
@@ -451,7 +467,7 @@ function walkDir(dir, files = [], maxDepth = 20, currentDepth = 0) {
|
|
|
451
467
|
continue;
|
|
452
468
|
if (entry.name === ".git")
|
|
453
469
|
continue;
|
|
454
|
-
const fullPath =
|
|
470
|
+
const fullPath = path6.join(dir, entry.name);
|
|
455
471
|
if (entry.isDirectory()) {
|
|
456
472
|
walkDir(fullPath, files, maxDepth, currentDepth + 1);
|
|
457
473
|
} else if (entry.isFile()) {
|
|
@@ -465,7 +481,7 @@ function walkDir(dir, files = [], maxDepth = 20, currentDepth = 0) {
|
|
|
465
481
|
var description5 = 'Find files matching a glob pattern. Supports patterns like "**/*.ts", "src/**/*.js", "*.{ts,tsx}". Excludes node_modules and hidden files by default.';
|
|
466
482
|
async function execute5({ pattern, cwd, maxResults = 1e3, includeHidden = false }) {
|
|
467
483
|
try {
|
|
468
|
-
const searchDir = cwd ?
|
|
484
|
+
const searchDir = cwd ? path6.isAbsolute(cwd) ? cwd : path6.resolve(process.cwd(), cwd) : process.cwd();
|
|
469
485
|
if (!fs5.existsSync(searchDir)) {
|
|
470
486
|
return {
|
|
471
487
|
success: false,
|
|
@@ -476,7 +492,7 @@ async function execute5({ pattern, cwd, maxResults = 1e3, includeHidden = false
|
|
|
476
492
|
const regex = globToRegex(pattern);
|
|
477
493
|
const matches = [];
|
|
478
494
|
for (const file of allFiles) {
|
|
479
|
-
const relativePath =
|
|
495
|
+
const relativePath = path6.relative(searchDir, file).replace(/\\/g, "/");
|
|
480
496
|
if (!includeHidden && relativePath.split("/").some((part) => part.startsWith("."))) {
|
|
481
497
|
continue;
|
|
482
498
|
}
|
|
@@ -510,7 +526,7 @@ async function execute5({ pattern, cwd, maxResults = 1e3, includeHidden = false
|
|
|
510
526
|
|
|
511
527
|
// ../packages/engine/src/tools/grep.js
|
|
512
528
|
import fs6 from "fs";
|
|
513
|
-
import
|
|
529
|
+
import path7 from "path";
|
|
514
530
|
function walkDir2(dir, files = [], maxDepth = 20, currentDepth = 0) {
|
|
515
531
|
if (currentDepth > maxDepth)
|
|
516
532
|
return files;
|
|
@@ -529,13 +545,13 @@ function walkDir2(dir, files = [], maxDepth = 20, currentDepth = 0) {
|
|
|
529
545
|
continue;
|
|
530
546
|
if (entry.name === ".next")
|
|
531
547
|
continue;
|
|
532
|
-
const fullPath =
|
|
548
|
+
const fullPath = path7.join(dir, entry.name);
|
|
533
549
|
if (entry.isDirectory()) {
|
|
534
550
|
if (!entry.name.startsWith(".")) {
|
|
535
551
|
walkDir2(fullPath, files, maxDepth, currentDepth + 1);
|
|
536
552
|
}
|
|
537
553
|
} else if (entry.isFile()) {
|
|
538
|
-
const ext =
|
|
554
|
+
const ext = path7.extname(entry.name).toLowerCase();
|
|
539
555
|
const binaryExts = [
|
|
540
556
|
".png",
|
|
541
557
|
".jpg",
|
|
@@ -609,7 +625,7 @@ async function execute6({ pattern, path: searchPath, filePattern, ignoreCase = f
|
|
|
609
625
|
error: `Invalid regex pattern: ${err.message}`
|
|
610
626
|
};
|
|
611
627
|
}
|
|
612
|
-
const targetPath = searchPath ?
|
|
628
|
+
const targetPath = searchPath ? path7.isAbsolute(searchPath) ? searchPath : path7.resolve(process.cwd(), searchPath) : process.cwd();
|
|
613
629
|
if (!fs6.existsSync(targetPath)) {
|
|
614
630
|
return {
|
|
615
631
|
success: false,
|
|
@@ -634,7 +650,7 @@ async function execute6({ pattern, path: searchPath, filePattern, ignoreCase = f
|
|
|
634
650
|
break;
|
|
635
651
|
const matches = searchFile(file, regex, contextLines);
|
|
636
652
|
if (matches.length > 0) {
|
|
637
|
-
const relativePath =
|
|
653
|
+
const relativePath = path7.relative(targetPath, file).replace(/\\/g, "/") || path7.basename(file);
|
|
638
654
|
for (const match of matches) {
|
|
639
655
|
if (totalMatches >= maxResults)
|
|
640
656
|
break;
|
|
@@ -680,7 +696,7 @@ async function execute6({ pattern, path: searchPath, filePattern, ignoreCase = f
|
|
|
680
696
|
|
|
681
697
|
// ../packages/engine/src/tools/ls.js
|
|
682
698
|
import fs7 from "fs";
|
|
683
|
-
import
|
|
699
|
+
import path8 from "path";
|
|
684
700
|
var description7 = "List directory contents in a tree format. Shows file names, types, and sizes. More efficient than bash ls \u2014 no subprocess needed, returns structured output.";
|
|
685
701
|
var DEFAULT_IGNORE = ["node_modules", ".git", "__pycache__", ".next", ".nuxt"];
|
|
686
702
|
function shouldIgnore(name, ignorePatterns) {
|
|
@@ -722,7 +738,7 @@ function buildTree(dirPath, prefix, depth, maxDepth, ignorePatterns, state) {
|
|
|
722
738
|
const isLast = i === entries.length - 1;
|
|
723
739
|
const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 ";
|
|
724
740
|
const childPrefix = isLast ? " " : "\u2502 ";
|
|
725
|
-
const fullPath =
|
|
741
|
+
const fullPath = path8.join(dirPath, entry.name);
|
|
726
742
|
if (entry.isDirectory()) {
|
|
727
743
|
state.dirs++;
|
|
728
744
|
state.lines.push(`${prefix}${connector}${entry.name}/`);
|
|
@@ -831,7 +847,7 @@ async function execute8({ url, format = "markdown", timeout = 3e4 }) {
|
|
|
831
847
|
|
|
832
848
|
// ../packages/engine/src/tools/patch.js
|
|
833
849
|
import fs8 from "fs";
|
|
834
|
-
import
|
|
850
|
+
import path9 from "path";
|
|
835
851
|
var description9 = "Apply a unified diff patch to one or more files atomically. All hunks are validated before any changes are written. If any hunk fails to apply, no files are modified. Use standard unified diff format (output of `git diff` or `diff -u`).";
|
|
836
852
|
function parsePatch(patchText) {
|
|
837
853
|
const patches = [];
|
|
@@ -986,7 +1002,7 @@ ${errors.join("\n")}`,
|
|
|
986
1002
|
const filesCreated = [];
|
|
987
1003
|
const filesDeleted = [];
|
|
988
1004
|
for (const [filePath, content] of pendingWrites) {
|
|
989
|
-
const dir =
|
|
1005
|
+
const dir = path9.dirname(filePath);
|
|
990
1006
|
if (!fs8.existsSync(dir)) {
|
|
991
1007
|
fs8.mkdirSync(dir, { recursive: true });
|
|
992
1008
|
}
|
|
@@ -1018,7 +1034,7 @@ ${errors.join("\n")}`,
|
|
|
1018
1034
|
import { streamText, stepCountIs } from "ai";
|
|
1019
1035
|
var description10 = "Spawn a read-only sub-agent to explore the codebase. The sub-agent can read files, search with glob/grep, and list directories, but cannot write files, edit, or run commands. Use this for parallel codebase exploration \u2014 understanding architecture, finding patterns, or researching how something works before making changes.";
|
|
1020
1036
|
function createSubAgentExecutor(model, workingDir, readOnlyTools) {
|
|
1021
|
-
return async function
|
|
1037
|
+
return async function execute13({ prompt, maxTurns = 20 }) {
|
|
1022
1038
|
try {
|
|
1023
1039
|
let turnsUsed = 0;
|
|
1024
1040
|
const stream = streamText({
|
|
@@ -1100,13 +1116,103 @@ async function execute10({ action, args, cwd }) {
|
|
|
1100
1116
|
}
|
|
1101
1117
|
}
|
|
1102
1118
|
|
|
1119
|
+
// ../packages/engine/src/tools/web-search.js
|
|
1120
|
+
var description12 = "Search the web for documentation, error messages, library usage, or any information. Returns search results with titles, URLs, and snippets. Use this when you need up-to-date information that might not be in your training data.";
|
|
1121
|
+
async function execute11(input) {
|
|
1122
|
+
const { query, maxResults = 8 } = input;
|
|
1123
|
+
try {
|
|
1124
|
+
const encoded = encodeURIComponent(query);
|
|
1125
|
+
const response = await globalThis.fetch(`https://html.duckduckgo.com/html/?q=${encoded}`, {
|
|
1126
|
+
headers: {
|
|
1127
|
+
"User-Agent": "WorkerMill/1.0 (AI Coding Agent)"
|
|
1128
|
+
},
|
|
1129
|
+
signal: AbortSignal.timeout(15e3)
|
|
1130
|
+
});
|
|
1131
|
+
if (!response.ok) {
|
|
1132
|
+
return { success: false, error: `Search failed: HTTP ${response.status}` };
|
|
1133
|
+
}
|
|
1134
|
+
const html = await response.text();
|
|
1135
|
+
const results = [];
|
|
1136
|
+
const resultPattern = /<a[^>]*class="result__a"[^>]*href="([^"]*)"[^>]*>([\s\S]*?)<\/a>[\s\S]*?<a[^>]*class="result__snippet"[^>]*>([\s\S]*?)<\/a>/g;
|
|
1137
|
+
let match;
|
|
1138
|
+
while ((match = resultPattern.exec(html)) !== null && results.length < maxResults) {
|
|
1139
|
+
const rawUrl = match[1];
|
|
1140
|
+
const title = match[2].replace(/<[^>]+>/g, "").trim();
|
|
1141
|
+
const snippet = match[3].replace(/<[^>]+>/g, "").trim();
|
|
1142
|
+
let url = rawUrl;
|
|
1143
|
+
const uddgMatch = rawUrl.match(/uddg=([^&]+)/);
|
|
1144
|
+
if (uddgMatch) {
|
|
1145
|
+
url = decodeURIComponent(uddgMatch[1]);
|
|
1146
|
+
}
|
|
1147
|
+
if (title && url) {
|
|
1148
|
+
results.push({ title, url, snippet });
|
|
1149
|
+
}
|
|
1150
|
+
}
|
|
1151
|
+
if (results.length === 0) {
|
|
1152
|
+
return { success: true, results: [], error: "No results found" };
|
|
1153
|
+
}
|
|
1154
|
+
return { success: true, results };
|
|
1155
|
+
} catch (err) {
|
|
1156
|
+
return {
|
|
1157
|
+
success: false,
|
|
1158
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1159
|
+
};
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// ../packages/engine/src/tools/todo.js
|
|
1164
|
+
var description13 = "Track your progress within a task. Create todo items, mark them complete, and list status. Use this to stay organized on multi-step work.";
|
|
1165
|
+
var todos = /* @__PURE__ */ new Map();
|
|
1166
|
+
var nextId = 1;
|
|
1167
|
+
async function execute12(input) {
|
|
1168
|
+
const { action, text, id, status } = input;
|
|
1169
|
+
switch (action) {
|
|
1170
|
+
case "add": {
|
|
1171
|
+
if (!text)
|
|
1172
|
+
return { success: false, error: "Text is required for adding a todo" };
|
|
1173
|
+
const todoId = `todo-${nextId++}`;
|
|
1174
|
+
const item = {
|
|
1175
|
+
id: todoId,
|
|
1176
|
+
text,
|
|
1177
|
+
status: "pending",
|
|
1178
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
1179
|
+
};
|
|
1180
|
+
todos.set(todoId, item);
|
|
1181
|
+
return { success: true, item };
|
|
1182
|
+
}
|
|
1183
|
+
case "update": {
|
|
1184
|
+
if (!id)
|
|
1185
|
+
return { success: false, error: "ID is required for updating a todo" };
|
|
1186
|
+
const existing = todos.get(id);
|
|
1187
|
+
if (!existing)
|
|
1188
|
+
return { success: false, error: `Todo not found: ${id}` };
|
|
1189
|
+
if (status)
|
|
1190
|
+
existing.status = status;
|
|
1191
|
+
if (text)
|
|
1192
|
+
existing.text = text;
|
|
1193
|
+
return { success: true, item: existing };
|
|
1194
|
+
}
|
|
1195
|
+
case "list": {
|
|
1196
|
+
const items = Array.from(todos.values());
|
|
1197
|
+
return { success: true, items };
|
|
1198
|
+
}
|
|
1199
|
+
case "clear": {
|
|
1200
|
+
todos.clear();
|
|
1201
|
+
nextId = 1;
|
|
1202
|
+
return { success: true, items: [] };
|
|
1203
|
+
}
|
|
1204
|
+
default:
|
|
1205
|
+
return { success: false, error: `Unknown action: ${action}` };
|
|
1206
|
+
}
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1103
1209
|
// ../packages/engine/src/tools/index.js
|
|
1104
1210
|
function assertPathInBounds(resolvedPath, workingDir, sandboxed) {
|
|
1105
1211
|
if (!sandboxed)
|
|
1106
1212
|
return resolvedPath;
|
|
1107
|
-
const normalized =
|
|
1108
|
-
const normalizedWorkDir =
|
|
1109
|
-
if (!normalized.startsWith(normalizedWorkDir +
|
|
1213
|
+
const normalized = path10.resolve(resolvedPath);
|
|
1214
|
+
const normalizedWorkDir = path10.resolve(workingDir);
|
|
1215
|
+
if (!normalized.startsWith(normalizedWorkDir + path10.sep) && normalized !== normalizedWorkDir) {
|
|
1110
1216
|
throw new Error(`Path "${resolvedPath}" is outside the working directory. Use --full-disk to allow access outside ${workingDir}`);
|
|
1111
1217
|
}
|
|
1112
1218
|
return normalized;
|
|
@@ -1121,7 +1227,7 @@ function createToolDefinitions(workingDir, model, sandboxed = true) {
|
|
|
1121
1227
|
timeout: z.number().optional().describe("Timeout in milliseconds (default: 120000 = 2 minutes)")
|
|
1122
1228
|
}),
|
|
1123
1229
|
execute: async ({ command, cwd, timeout }) => {
|
|
1124
|
-
const resolvedCwd = cwd ?
|
|
1230
|
+
const resolvedCwd = cwd ? path10.isAbsolute(cwd) ? cwd : path10.resolve(workingDir, cwd) : workingDir;
|
|
1125
1231
|
assertPathInBounds(resolvedCwd, workingDir, sandboxed);
|
|
1126
1232
|
const result = await execute({
|
|
1127
1233
|
command,
|
|
@@ -1131,8 +1237,14 @@ function createToolDefinitions(workingDir, model, sandboxed = true) {
|
|
|
1131
1237
|
if (result.success) {
|
|
1132
1238
|
return result.stdout || "(no output)";
|
|
1133
1239
|
}
|
|
1134
|
-
|
|
1135
|
-
|
|
1240
|
+
const parts = [];
|
|
1241
|
+
if (result.stderr)
|
|
1242
|
+
parts.push(result.stderr);
|
|
1243
|
+
if (result.stdout)
|
|
1244
|
+
parts.push(result.stdout);
|
|
1245
|
+
if (result.error)
|
|
1246
|
+
parts.push(result.error);
|
|
1247
|
+
return `Error: ${parts.join("\n")}`.trim();
|
|
1136
1248
|
}
|
|
1137
1249
|
}),
|
|
1138
1250
|
read_file: tool({
|
|
@@ -1144,7 +1256,7 @@ ${result.stdout || ""}`.trim();
|
|
|
1144
1256
|
startLine: z.number().optional().describe("Line number to start reading from (1-indexed, optional)")
|
|
1145
1257
|
}),
|
|
1146
1258
|
execute: async ({ path: filePath, encoding, maxLines, startLine }) => {
|
|
1147
|
-
const resolvedPath =
|
|
1259
|
+
const resolvedPath = path10.isAbsolute(filePath) ? filePath : path10.resolve(workingDir, filePath);
|
|
1148
1260
|
assertPathInBounds(resolvedPath, workingDir, sandboxed);
|
|
1149
1261
|
const result = await execute2({
|
|
1150
1262
|
path: resolvedPath,
|
|
@@ -1167,7 +1279,7 @@ ${result.stdout || ""}`.trim();
|
|
|
1167
1279
|
append: z.boolean().optional().describe("Append to file instead of overwriting (default: false)")
|
|
1168
1280
|
}),
|
|
1169
1281
|
execute: async ({ path: filePath, content, encoding, append }) => {
|
|
1170
|
-
const resolvedPath =
|
|
1282
|
+
const resolvedPath = path10.isAbsolute(filePath) ? filePath : path10.resolve(workingDir, filePath);
|
|
1171
1283
|
assertPathInBounds(resolvedPath, workingDir, sandboxed);
|
|
1172
1284
|
const result = await execute3({
|
|
1173
1285
|
path: resolvedPath,
|
|
@@ -1190,7 +1302,7 @@ ${result.stdout || ""}`.trim();
|
|
|
1190
1302
|
replaceAll: z.boolean().optional().describe("Replace all occurrences instead of requiring unique match (default: false)")
|
|
1191
1303
|
}),
|
|
1192
1304
|
execute: async ({ path: filePath, old_string, new_string, replaceAll }) => {
|
|
1193
|
-
const resolvedPath =
|
|
1305
|
+
const resolvedPath = path10.isAbsolute(filePath) ? filePath : path10.resolve(workingDir, filePath);
|
|
1194
1306
|
assertPathInBounds(resolvedPath, workingDir, sandboxed);
|
|
1195
1307
|
const result = await execute4({
|
|
1196
1308
|
path: resolvedPath,
|
|
@@ -1214,7 +1326,7 @@ Hint: ${result.hint}` : ""}`;
|
|
|
1214
1326
|
includeHidden: z.boolean().optional().describe("Include hidden files (starting with .) (default: false)")
|
|
1215
1327
|
}),
|
|
1216
1328
|
execute: async ({ pattern, cwd, maxResults, includeHidden }) => {
|
|
1217
|
-
const resolvedCwd = cwd ?
|
|
1329
|
+
const resolvedCwd = cwd ? path10.isAbsolute(cwd) ? cwd : path10.resolve(workingDir, cwd) : workingDir;
|
|
1218
1330
|
assertPathInBounds(resolvedCwd, workingDir, sandboxed);
|
|
1219
1331
|
const result = await execute5({
|
|
1220
1332
|
pattern,
|
|
@@ -1243,7 +1355,7 @@ ${result.matches.join("\n")}`;
|
|
|
1243
1355
|
maxResults: z.number().optional().describe("Maximum number of total matches to return (default: 100)")
|
|
1244
1356
|
}),
|
|
1245
1357
|
execute: async ({ pattern, path: searchPath, filePattern, ignoreCase, contextLines, maxResults }) => {
|
|
1246
|
-
const resolvedPath = searchPath ?
|
|
1358
|
+
const resolvedPath = searchPath ? path10.isAbsolute(searchPath) ? searchPath : path10.resolve(workingDir, searchPath) : workingDir;
|
|
1247
1359
|
assertPathInBounds(resolvedPath, workingDir, sandboxed);
|
|
1248
1360
|
const result = await execute6({
|
|
1249
1361
|
pattern,
|
|
@@ -1279,7 +1391,7 @@ ${result.matches.join("\n")}`;
|
|
|
1279
1391
|
maxFiles: z.number().optional().describe("Maximum number of entries to return (default: 1000)")
|
|
1280
1392
|
}),
|
|
1281
1393
|
execute: async ({ path: dirPath, ignore, maxDepth, maxFiles }) => {
|
|
1282
|
-
const resolvedPath =
|
|
1394
|
+
const resolvedPath = path10.isAbsolute(dirPath) ? dirPath : path10.resolve(workingDir, dirPath);
|
|
1283
1395
|
assertPathInBounds(resolvedPath, workingDir, sandboxed);
|
|
1284
1396
|
const result = await execute7({ path: resolvedPath, ignore, maxDepth, maxFiles });
|
|
1285
1397
|
if (result.success) {
|
|
@@ -1342,6 +1454,49 @@ Hint: ${result.hint}` : ""}`;
|
|
|
1342
1454
|
return `Error: ${result.error}`;
|
|
1343
1455
|
}
|
|
1344
1456
|
}),
|
|
1457
|
+
web_search: tool({
|
|
1458
|
+
description: description12,
|
|
1459
|
+
inputSchema: z.object({
|
|
1460
|
+
query: z.string().describe("Search query \u2014 be specific, include library names, error messages, etc."),
|
|
1461
|
+
maxResults: z.number().optional().describe("Maximum results to return (default: 8)")
|
|
1462
|
+
}),
|
|
1463
|
+
execute: async ({ query, maxResults }) => {
|
|
1464
|
+
const result = await execute11({ query, maxResults });
|
|
1465
|
+
if (result.success && result.results && result.results.length > 0) {
|
|
1466
|
+
return result.results.map((r, i) => `${i + 1}. ${r.title}
|
|
1467
|
+
${r.url}
|
|
1468
|
+
${r.snippet}`).join("\n\n");
|
|
1469
|
+
}
|
|
1470
|
+
return result.error || "No results found";
|
|
1471
|
+
}
|
|
1472
|
+
}),
|
|
1473
|
+
todo: tool({
|
|
1474
|
+
description: description13,
|
|
1475
|
+
inputSchema: z.object({
|
|
1476
|
+
action: z.enum(["add", "update", "list", "clear"]).describe("Action to perform"),
|
|
1477
|
+
text: z.string().optional().describe("Todo text (for add/update)"),
|
|
1478
|
+
id: z.string().optional().describe("Todo ID (for update)"),
|
|
1479
|
+
status: z.enum(["pending", "in_progress", "completed"]).optional().describe("New status (for update)")
|
|
1480
|
+
}),
|
|
1481
|
+
execute: async ({ action, text, id, status }) => {
|
|
1482
|
+
const result = await execute12({ action, text, id, status });
|
|
1483
|
+
if (result.success) {
|
|
1484
|
+
if (result.item) {
|
|
1485
|
+
return `[${result.item.status}] ${result.item.id}: ${result.item.text}`;
|
|
1486
|
+
}
|
|
1487
|
+
if (result.items) {
|
|
1488
|
+
if (result.items.length === 0)
|
|
1489
|
+
return "No todos";
|
|
1490
|
+
const pending = result.items.filter((t) => t.status !== "completed").length;
|
|
1491
|
+
const done = result.items.filter((t) => t.status === "completed").length;
|
|
1492
|
+
return result.items.map((t) => `[${t.status === "completed" ? "\u2713" : t.status === "in_progress" ? "\u2192" : " "}] ${t.id}: ${t.text}`).join("\n") + `
|
|
1493
|
+
|
|
1494
|
+
${done}/${result.items.length} completed, ${pending} remaining`;
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
return result.error || "Unknown error";
|
|
1498
|
+
}
|
|
1499
|
+
}),
|
|
1345
1500
|
...model ? {
|
|
1346
1501
|
sub_agent: tool({
|
|
1347
1502
|
description: description10,
|
|
@@ -1359,7 +1514,7 @@ Hint: ${result.hint}` : ""}`;
|
|
|
1359
1514
|
startLine: z.number().optional().describe("Start line (1-indexed)")
|
|
1360
1515
|
}),
|
|
1361
1516
|
execute: async ({ path: filePath, maxLines, startLine }) => {
|
|
1362
|
-
const resolvedPath =
|
|
1517
|
+
const resolvedPath = path10.isAbsolute(filePath) ? filePath : path10.resolve(workingDir, filePath);
|
|
1363
1518
|
assertPathInBounds(resolvedPath, workingDir, sandboxed);
|
|
1364
1519
|
const result2 = await execute2({ path: resolvedPath, maxLines, startLine });
|
|
1365
1520
|
return result2.success ? result2.content || "" : `Error: ${result2.error}`;
|
|
@@ -1372,7 +1527,7 @@ Hint: ${result.hint}` : ""}`;
|
|
|
1372
1527
|
cwd: z.string().optional().describe("Directory to search in")
|
|
1373
1528
|
}),
|
|
1374
1529
|
execute: async ({ pattern, cwd }) => {
|
|
1375
|
-
const resolvedCwd = cwd ?
|
|
1530
|
+
const resolvedCwd = cwd ? path10.isAbsolute(cwd) ? cwd : path10.resolve(workingDir, cwd) : workingDir;
|
|
1376
1531
|
assertPathInBounds(resolvedCwd, workingDir, sandboxed);
|
|
1377
1532
|
const result2 = await execute5({ pattern, cwd: resolvedCwd });
|
|
1378
1533
|
return result2.success ? result2.count === 0 ? `No files found matching: ${pattern}` : `Found ${result2.count} file(s):
|
|
@@ -1387,7 +1542,7 @@ ${result2.matches.join("\n")}` : `Error: ${result2.error}`;
|
|
|
1387
1542
|
filePattern: z.string().optional().describe("Glob to filter files")
|
|
1388
1543
|
}),
|
|
1389
1544
|
execute: async ({ pattern, path: searchPath, filePattern }) => {
|
|
1390
|
-
const resolvedPath = searchPath ?
|
|
1545
|
+
const resolvedPath = searchPath ? path10.isAbsolute(searchPath) ? searchPath : path10.resolve(workingDir, searchPath) : workingDir;
|
|
1391
1546
|
assertPathInBounds(resolvedPath, workingDir, sandboxed);
|
|
1392
1547
|
const result2 = await execute6({ pattern, path: resolvedPath, filePattern });
|
|
1393
1548
|
if (!result2.success)
|
|
@@ -1410,7 +1565,7 @@ ${result2.matches.join("\n")}` : `Error: ${result2.error}`;
|
|
|
1410
1565
|
maxDepth: z.number().optional().describe("Max depth (default: 3)")
|
|
1411
1566
|
}),
|
|
1412
1567
|
execute: async ({ path: dirPath, maxDepth }) => {
|
|
1413
|
-
const resolvedPath =
|
|
1568
|
+
const resolvedPath = path10.isAbsolute(dirPath) ? dirPath : path10.resolve(workingDir, dirPath);
|
|
1414
1569
|
assertPathInBounds(resolvedPath, workingDir, sandboxed);
|
|
1415
1570
|
const result2 = await execute7({ path: resolvedPath, maxDepth });
|
|
1416
1571
|
return result2.success ? result2.tree : `Error: ${result2.error}`;
|
|
@@ -1431,422 +1586,21 @@ ${result.content}`;
|
|
|
1431
1586
|
};
|
|
1432
1587
|
}
|
|
1433
1588
|
|
|
1434
|
-
// src/permissions.js
|
|
1435
|
-
import readline from "readline";
|
|
1436
|
-
import chalk from "chalk";
|
|
1437
|
-
var READ_TOOLS = /* @__PURE__ */ new Set(["read_file", "glob", "grep", "ls", "sub_agent"]);
|
|
1438
|
-
var DANGEROUS_PATTERNS = [
|
|
1439
|
-
{ pattern: /rm\s+(-[a-z]*f|-[a-z]*r|--force|--recursive)/i, label: "recursive/forced delete" },
|
|
1440
|
-
{ pattern: /git\s+reset\s+--hard/i, label: "hard reset" },
|
|
1441
|
-
{ pattern: /git\s+push\s+.*--force/i, label: "force push" },
|
|
1442
|
-
{ pattern: /git\s+clean\s+-[a-z]*f/i, label: "git clean" },
|
|
1443
|
-
{ pattern: /drop\s+table/i, label: "drop table" },
|
|
1444
|
-
{ pattern: /truncate\s+/i, label: "truncate" },
|
|
1445
|
-
{ pattern: /DELETE\s+FROM\s+\w+\s*;/i, label: "DELETE without WHERE" },
|
|
1446
|
-
{ pattern: /chmod\s+777/i, label: "chmod 777" },
|
|
1447
|
-
{ pattern: />(\/dev\/sda|\/dev\/disk)/i, label: "write to disk device" }
|
|
1448
|
-
];
|
|
1449
|
-
function isDangerous(command) {
|
|
1450
|
-
for (const { pattern, label } of DANGEROUS_PATTERNS) {
|
|
1451
|
-
if (pattern.test(command))
|
|
1452
|
-
return label;
|
|
1453
|
-
}
|
|
1454
|
-
return null;
|
|
1455
|
-
}
|
|
1456
|
-
var PermissionManager = class {
|
|
1457
|
-
sessionAllow = /* @__PURE__ */ new Set();
|
|
1458
|
-
trustAll;
|
|
1459
|
-
configTrust;
|
|
1460
|
-
rl = null;
|
|
1461
|
-
cancelCurrentPrompt = null;
|
|
1462
|
-
/** True while rl.question() is active — external line handlers must ignore input */
|
|
1463
|
-
questionActive = false;
|
|
1464
|
-
constructor(trustAll = false, configTrust = []) {
|
|
1465
|
-
this.trustAll = trustAll;
|
|
1466
|
-
this.configTrust = new Set(configTrust);
|
|
1467
|
-
}
|
|
1468
|
-
/** Bind to the agent's readline instance so we reuse it for prompts */
|
|
1469
|
-
setReadline(rl) {
|
|
1470
|
-
this.rl = rl;
|
|
1471
|
-
}
|
|
1472
|
-
cancelPrompt() {
|
|
1473
|
-
if (this.cancelCurrentPrompt) {
|
|
1474
|
-
this.cancelCurrentPrompt();
|
|
1475
|
-
this.cancelCurrentPrompt = null;
|
|
1476
|
-
}
|
|
1477
|
-
}
|
|
1478
|
-
async checkPermission(toolName, toolInput) {
|
|
1479
|
-
if (toolName === "bash") {
|
|
1480
|
-
const cmd = String(toolInput.command || "");
|
|
1481
|
-
const danger = isDangerous(cmd);
|
|
1482
|
-
if (danger) {
|
|
1483
|
-
console.log();
|
|
1484
|
-
console.log(chalk.red.bold(` \u26A0 DANGEROUS: ${danger}`));
|
|
1485
|
-
console.log(chalk.red(` Command: ${cmd}`));
|
|
1486
|
-
if (this.trustAll) {
|
|
1487
|
-
console.log(chalk.yellow(" (allowed by --trust mode)"));
|
|
1488
|
-
return true;
|
|
1489
|
-
}
|
|
1490
|
-
const answer = await this.askUser(chalk.red(" Are you sure? (yes to confirm): "));
|
|
1491
|
-
if (answer.trim().toLowerCase() !== "yes")
|
|
1492
|
-
return false;
|
|
1493
|
-
return true;
|
|
1494
|
-
}
|
|
1495
|
-
}
|
|
1496
|
-
if (this.trustAll)
|
|
1497
|
-
return true;
|
|
1498
|
-
if (READ_TOOLS.has(toolName))
|
|
1499
|
-
return true;
|
|
1500
|
-
if (this.sessionAllow.has(toolName))
|
|
1501
|
-
return true;
|
|
1502
|
-
if (this.configTrust.has(toolName))
|
|
1503
|
-
return true;
|
|
1504
|
-
return this.promptUser(toolName, toolInput);
|
|
1505
|
-
}
|
|
1506
|
-
async promptUser(toolName, toolInput) {
|
|
1507
|
-
const display = this.formatToolCall(toolName, toolInput);
|
|
1508
|
-
console.log();
|
|
1509
|
-
console.log(chalk.cyan(` \u250C\u2500 ${toolName} ${"\u2500".repeat(Math.max(0, 40 - toolName.length))}\u2510`));
|
|
1510
|
-
for (const line of display.split("\n")) {
|
|
1511
|
-
console.log(chalk.cyan(" \u2502 ") + chalk.white(line));
|
|
1512
|
-
}
|
|
1513
|
-
console.log(chalk.cyan(` \u2514${"\u2500".repeat(43)}\u2518`));
|
|
1514
|
-
const answer = await this.askUser(chalk.dim(" Allow? ") + chalk.white("(y)es / (n)o / (a)lways this tool / (t)rust all: "));
|
|
1515
|
-
const choice = answer.trim().toLowerCase();
|
|
1516
|
-
if (choice === "t" || choice === "trust") {
|
|
1517
|
-
this.trustAll = true;
|
|
1518
|
-
return true;
|
|
1519
|
-
}
|
|
1520
|
-
if (choice === "a" || choice === "always") {
|
|
1521
|
-
this.sessionAllow.add(toolName);
|
|
1522
|
-
return true;
|
|
1523
|
-
}
|
|
1524
|
-
return choice === "y" || choice === "yes";
|
|
1525
|
-
}
|
|
1526
|
-
/**
|
|
1527
|
-
* Prompt the user with a question. Sets questionActive flag so the
|
|
1528
|
-
* agent's line handler knows to ignore this input.
|
|
1529
|
-
*/
|
|
1530
|
-
askUser(prompt) {
|
|
1531
|
-
return new Promise((resolve, reject) => {
|
|
1532
|
-
this.cancelCurrentPrompt = () => {
|
|
1533
|
-
this.questionActive = false;
|
|
1534
|
-
reject(new Error("cancelled"));
|
|
1535
|
-
};
|
|
1536
|
-
if (this.rl) {
|
|
1537
|
-
this.questionActive = true;
|
|
1538
|
-
this.rl.resume();
|
|
1539
|
-
this.rl.question(prompt, (answer) => {
|
|
1540
|
-
this.questionActive = false;
|
|
1541
|
-
this.cancelCurrentPrompt = null;
|
|
1542
|
-
this.rl.pause();
|
|
1543
|
-
resolve(answer);
|
|
1544
|
-
});
|
|
1545
|
-
} else {
|
|
1546
|
-
const questionRl = readline.createInterface({
|
|
1547
|
-
input: process.stdin,
|
|
1548
|
-
output: process.stdout
|
|
1549
|
-
});
|
|
1550
|
-
this.questionActive = true;
|
|
1551
|
-
questionRl.question(prompt, (answer) => {
|
|
1552
|
-
this.questionActive = false;
|
|
1553
|
-
this.cancelCurrentPrompt = null;
|
|
1554
|
-
questionRl.close();
|
|
1555
|
-
resolve(answer);
|
|
1556
|
-
});
|
|
1557
|
-
}
|
|
1558
|
-
});
|
|
1559
|
-
}
|
|
1560
|
-
formatToolCall(toolName, input) {
|
|
1561
|
-
switch (toolName) {
|
|
1562
|
-
case "bash":
|
|
1563
|
-
return String(input.command || "");
|
|
1564
|
-
case "write_file":
|
|
1565
|
-
case "edit_file":
|
|
1566
|
-
return `${input.path || ""}`;
|
|
1567
|
-
case "patch":
|
|
1568
|
-
return String(input.patch_text || "").slice(0, 200) + "...";
|
|
1569
|
-
case "fetch":
|
|
1570
|
-
return String(input.url || "");
|
|
1571
|
-
default:
|
|
1572
|
-
return JSON.stringify(input, null, 2).slice(0, 200);
|
|
1573
|
-
}
|
|
1574
|
-
}
|
|
1575
|
-
};
|
|
1576
|
-
|
|
1577
|
-
// src/tui.js
|
|
1578
|
-
import chalk2 from "chalk";
|
|
1579
|
-
import { execSync as execSync2 } from "child_process";
|
|
1580
|
-
var toolCounts = {};
|
|
1581
|
-
var cachedGitBranch = "";
|
|
1582
|
-
var lastBranchCheck = 0;
|
|
1583
|
-
function getGitBranch() {
|
|
1584
|
-
const now = Date.now();
|
|
1585
|
-
if (now - lastBranchCheck > 1e4) {
|
|
1586
|
-
lastBranchCheck = now;
|
|
1587
|
-
try {
|
|
1588
|
-
cachedGitBranch = execSync2("git rev-parse --abbrev-ref HEAD 2>/dev/null", { encoding: "utf-8", timeout: 2e3 }).trim();
|
|
1589
|
-
} catch {
|
|
1590
|
-
cachedGitBranch = "";
|
|
1591
|
-
}
|
|
1592
|
-
}
|
|
1593
|
-
return cachedGitBranch;
|
|
1594
|
-
}
|
|
1595
|
-
function incrementToolCount(toolName) {
|
|
1596
|
-
toolCounts[toolName] = (toolCounts[toolName] || 0) + 1;
|
|
1597
|
-
}
|
|
1598
|
-
function printHeader(version, provider, model, cwd) {
|
|
1599
|
-
console.log();
|
|
1600
|
-
console.log(chalk2.bold.white(` WorkerMill`) + chalk2.dim(` v${version}`));
|
|
1601
|
-
if (provider && model) {
|
|
1602
|
-
console.log(chalk2.dim(` ${provider}/`) + chalk2.white(model));
|
|
1603
|
-
}
|
|
1604
|
-
if (cwd) {
|
|
1605
|
-
console.log(chalk2.dim(` cwd: `) + chalk2.white(cwd));
|
|
1606
|
-
}
|
|
1607
|
-
console.log(chalk2.dim(` /help`) + chalk2.dim(` for commands, `) + chalk2.dim(`Ctrl+C`) + chalk2.dim(` to cancel`));
|
|
1608
|
-
console.log();
|
|
1609
|
-
}
|
|
1610
|
-
function printToolCall(toolName, toolInput) {
|
|
1611
|
-
incrementToolCount(toolName);
|
|
1612
|
-
const arrow = chalk2.dim(" \u2193 ");
|
|
1613
|
-
const label = chalk2.cyan;
|
|
1614
|
-
switch (toolName) {
|
|
1615
|
-
case "bash": {
|
|
1616
|
-
let cmd = String(toolInput.command || "");
|
|
1617
|
-
const heredocIdx = cmd.indexOf("<<");
|
|
1618
|
-
if (heredocIdx > 0)
|
|
1619
|
-
cmd = cmd.slice(0, heredocIdx).trim() + " << ...";
|
|
1620
|
-
if (cmd.length > 120)
|
|
1621
|
-
cmd = cmd.slice(0, 117) + "...";
|
|
1622
|
-
console.log(arrow + label("Bash ") + chalk2.yellow(cmd));
|
|
1623
|
-
break;
|
|
1624
|
-
}
|
|
1625
|
-
case "read_file":
|
|
1626
|
-
console.log(arrow + label("Read ") + chalk2.white(String(toolInput.path || "")));
|
|
1627
|
-
break;
|
|
1628
|
-
case "write_file":
|
|
1629
|
-
console.log(arrow + label("Write ") + chalk2.white(String(toolInput.path || "")));
|
|
1630
|
-
break;
|
|
1631
|
-
case "edit_file":
|
|
1632
|
-
console.log(arrow + label("Edit ") + chalk2.white(String(toolInput.path || "")));
|
|
1633
|
-
break;
|
|
1634
|
-
case "patch":
|
|
1635
|
-
console.log(arrow + label("Patch ") + chalk2.white("(multi-file)"));
|
|
1636
|
-
break;
|
|
1637
|
-
case "glob":
|
|
1638
|
-
console.log(arrow + label("Glob ") + chalk2.white(String(toolInput.pattern || "")));
|
|
1639
|
-
break;
|
|
1640
|
-
case "grep":
|
|
1641
|
-
console.log(arrow + label("Grep ") + chalk2.white(String(toolInput.pattern || "")));
|
|
1642
|
-
break;
|
|
1643
|
-
case "ls":
|
|
1644
|
-
console.log(arrow + label("List ") + chalk2.white(String(toolInput.path || ".")));
|
|
1645
|
-
break;
|
|
1646
|
-
case "fetch":
|
|
1647
|
-
console.log(arrow + label("Fetch ") + chalk2.white(String(toolInput.url || "")));
|
|
1648
|
-
break;
|
|
1649
|
-
case "sub_agent":
|
|
1650
|
-
console.log(arrow + label("Agent ") + chalk2.white(String(toolInput.prompt || "").slice(0, 80)));
|
|
1651
|
-
break;
|
|
1652
|
-
case "git":
|
|
1653
|
-
console.log(arrow + label("Git ") + chalk2.white(`${toolInput.action}${toolInput.args ? " " + toolInput.args : ""}`));
|
|
1654
|
-
break;
|
|
1655
|
-
default:
|
|
1656
|
-
console.log(arrow + label(toolName));
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
var PERSONA_EMOJIS = {
|
|
1660
|
-
frontend_developer: "\u{1F3A8}",
|
|
1661
|
-
// 🎨
|
|
1662
|
-
backend_developer: "\u{1F4BB}",
|
|
1663
|
-
// 💻
|
|
1664
|
-
fullstack_developer: "\u{1F4BB}",
|
|
1665
|
-
// 💻 (same as backend)
|
|
1666
|
-
devops_engineer: "\u{1F527}",
|
|
1667
|
-
// 🔧
|
|
1668
|
-
security_engineer: "\u{1F512}",
|
|
1669
|
-
// 🔐
|
|
1670
|
-
qa_engineer: "\u{1F9EA}",
|
|
1671
|
-
// 🧪
|
|
1672
|
-
tech_writer: "\u{1F4DD}",
|
|
1673
|
-
// 📝
|
|
1674
|
-
project_manager: "\u{1F4CB}",
|
|
1675
|
-
// 📋
|
|
1676
|
-
architect: "\u{1F3D7}\uFE0F",
|
|
1677
|
-
// 🏗️
|
|
1678
|
-
database_engineer: "\u{1F4CA}",
|
|
1679
|
-
// 📊
|
|
1680
|
-
data_engineer: "\u{1F4CA}",
|
|
1681
|
-
// 📊
|
|
1682
|
-
data_ml_engineer: "\u{1F4CA}",
|
|
1683
|
-
// 📊
|
|
1684
|
-
ml_engineer: "\u{1F4CA}",
|
|
1685
|
-
// 📊
|
|
1686
|
-
mobile_developer: "\u{1F4F1}",
|
|
1687
|
-
// 📱
|
|
1688
|
-
tech_lead: "\u{1F451}",
|
|
1689
|
-
// 👑
|
|
1690
|
-
manager: "\u{1F454}",
|
|
1691
|
-
// 👔
|
|
1692
|
-
support_agent: "\u{1F4AC}",
|
|
1693
|
-
// 💬
|
|
1694
|
-
planner: "\u{1F4A1}",
|
|
1695
|
-
// 💡 (planning_agent)
|
|
1696
|
-
coordinator: "\u{1F3AF}",
|
|
1697
|
-
// 🎯
|
|
1698
|
-
critic: "\u{1F50D}",
|
|
1699
|
-
// 🔍
|
|
1700
|
-
reviewer: "\u{1F50D}"
|
|
1701
|
-
// 🔍
|
|
1702
|
-
};
|
|
1703
|
-
function getPersonaEmoji(persona) {
|
|
1704
|
-
return PERSONA_EMOJIS[persona] || "\u{1F916}";
|
|
1705
|
-
}
|
|
1706
|
-
function printToolResult(toolName, result) {
|
|
1707
|
-
const isError = result.startsWith("Error:") || result.startsWith("error:");
|
|
1708
|
-
const lines = result.split("\n").filter((l) => l.trim());
|
|
1709
|
-
if (isError) {
|
|
1710
|
-
const errLines = lines.slice(0, 5);
|
|
1711
|
-
for (const line of errLines) {
|
|
1712
|
-
console.log(chalk2.red(` ${line}`));
|
|
1713
|
-
}
|
|
1714
|
-
if (lines.length > 5)
|
|
1715
|
-
console.log(chalk2.red(` ... ${lines.length - 5} more lines`));
|
|
1716
|
-
return;
|
|
1717
|
-
}
|
|
1718
|
-
switch (toolName) {
|
|
1719
|
-
case "read_file":
|
|
1720
|
-
console.log(chalk2.dim(` (${lines.length} lines)`));
|
|
1721
|
-
break;
|
|
1722
|
-
case "write_file":
|
|
1723
|
-
case "edit_file":
|
|
1724
|
-
case "patch":
|
|
1725
|
-
if (lines.length === 1) {
|
|
1726
|
-
console.log(chalk2.dim(` ${lines[0]}`));
|
|
1727
|
-
} else {
|
|
1728
|
-
console.log(chalk2.dim(` (${lines.length} lines changed)`));
|
|
1729
|
-
}
|
|
1730
|
-
break;
|
|
1731
|
-
case "bash": {
|
|
1732
|
-
if (lines.length === 0)
|
|
1733
|
-
break;
|
|
1734
|
-
const shown = lines.slice(0, 5);
|
|
1735
|
-
for (const line of shown) {
|
|
1736
|
-
console.log(chalk2.dim(` ${line}`));
|
|
1737
|
-
}
|
|
1738
|
-
if (lines.length > 5)
|
|
1739
|
-
console.log(chalk2.dim(` ... ${lines.length - 5} more lines`));
|
|
1740
|
-
break;
|
|
1741
|
-
}
|
|
1742
|
-
case "glob":
|
|
1743
|
-
case "grep":
|
|
1744
|
-
case "ls":
|
|
1745
|
-
const searchShown = lines.slice(0, 8);
|
|
1746
|
-
for (const line of searchShown) {
|
|
1747
|
-
console.log(chalk2.dim(` ${line}`));
|
|
1748
|
-
}
|
|
1749
|
-
if (lines.length > 8)
|
|
1750
|
-
console.log(chalk2.dim(` ... ${lines.length - 8} more`));
|
|
1751
|
-
break;
|
|
1752
|
-
default:
|
|
1753
|
-
if (lines.length <= 3) {
|
|
1754
|
-
for (const line of lines)
|
|
1755
|
-
console.log(chalk2.dim(` ${line}`));
|
|
1756
|
-
} else {
|
|
1757
|
-
console.log(chalk2.dim(` (${lines.length} lines)`));
|
|
1758
|
-
}
|
|
1759
|
-
}
|
|
1760
|
-
}
|
|
1761
|
-
function wmLog(persona, message) {
|
|
1762
|
-
const emoji = getPersonaEmoji(persona);
|
|
1763
|
-
console.log(chalk2.cyan(`[${emoji} ${persona} \u{1F3E0}] `) + chalk2.white(message));
|
|
1764
|
-
}
|
|
1765
|
-
function wmLogPrefix(persona) {
|
|
1766
|
-
const emoji = getPersonaEmoji(persona);
|
|
1767
|
-
return chalk2.cyan(`[${emoji} ${persona} \u{1F3E0}] `);
|
|
1768
|
-
}
|
|
1769
|
-
function wmCoordinatorLog(message) {
|
|
1770
|
-
console.log(chalk2.cyan("[coordinator] ") + chalk2.white(message));
|
|
1771
|
-
}
|
|
1772
|
-
function printError(message) {
|
|
1773
|
-
console.log(chalk2.red(`
|
|
1774
|
-
\u2717 ${message}
|
|
1775
|
-
`));
|
|
1776
|
-
}
|
|
1777
|
-
var sessionStartTime = Date.now();
|
|
1778
|
-
function formatTokens(n) {
|
|
1779
|
-
if (n >= 1e6)
|
|
1780
|
-
return `${(n / 1e6).toFixed(1)}M`;
|
|
1781
|
-
if (n >= 1e3)
|
|
1782
|
-
return `${(n / 1e3).toFixed(n >= 1e4 ? 0 : 1)}k`;
|
|
1783
|
-
return String(n);
|
|
1784
|
-
}
|
|
1785
|
-
function formatElapsed() {
|
|
1786
|
-
const mins = Math.floor((Date.now() - sessionStartTime) / 6e4);
|
|
1787
|
-
if (mins < 1)
|
|
1788
|
-
return "<1m";
|
|
1789
|
-
return `>${mins}m`;
|
|
1790
|
-
}
|
|
1791
|
-
function contextBar(tokens, maxContext) {
|
|
1792
|
-
const barLen = 8;
|
|
1793
|
-
const usage = Math.min(1, tokens / maxContext);
|
|
1794
|
-
const filled = Math.round(usage * barLen);
|
|
1795
|
-
const empty = barLen - filled;
|
|
1796
|
-
const color = usage < 0.5 ? chalk2.green : usage < 0.8 ? chalk2.yellow : chalk2.red;
|
|
1797
|
-
return color("\u2588".repeat(filled)) + chalk2.dim("\u2591".repeat(empty));
|
|
1798
|
-
}
|
|
1799
|
-
function printStatusBar(provider, model, tokens, permissionMode, cost) {
|
|
1800
|
-
const width = process.stdout.columns || 80;
|
|
1801
|
-
const bg = chalk2.bgRgb(30, 30, 30);
|
|
1802
|
-
const modelDisplay = ` ${model} `;
|
|
1803
|
-
const maxCtx = 128e3;
|
|
1804
|
-
const bar = contextBar(tokens, maxCtx);
|
|
1805
|
-
const tokStr = formatTokens(tokens);
|
|
1806
|
-
const shortNames = {
|
|
1807
|
-
bash: "Bash",
|
|
1808
|
-
read_file: "Read",
|
|
1809
|
-
write_file: "Write",
|
|
1810
|
-
edit_file: "Edit",
|
|
1811
|
-
glob: "Glob",
|
|
1812
|
-
grep: "Grep",
|
|
1813
|
-
ls: "List",
|
|
1814
|
-
fetch: "Fetch",
|
|
1815
|
-
patch: "Patch",
|
|
1816
|
-
sub_agent: "Agent",
|
|
1817
|
-
git: "Git"
|
|
1818
|
-
};
|
|
1819
|
-
const countParts = Object.entries(toolCounts).filter(([_, count]) => count > 0).map(([name, count]) => `${shortNames[name] || name} x${count}`);
|
|
1820
|
-
const toolStr = countParts.length > 0 ? countParts.join(" | ") : "";
|
|
1821
|
-
const gitBranch = getGitBranch();
|
|
1822
|
-
const cwd = process.cwd().split("/").pop() || "";
|
|
1823
|
-
const costStr = cost !== void 0 && cost > 0 ? `$${cost.toFixed(4)} | ` : "";
|
|
1824
|
-
const elapsed = formatElapsed();
|
|
1825
|
-
const modeStr = permissionMode || "ask";
|
|
1826
|
-
const left = bg.white(modelDisplay) + " " + bar + " " + bg.white(tokStr);
|
|
1827
|
-
const middle = gitBranch ? bg.dim(" | ") + bg.white(cwd) + bg.dim(" git:(") + bg.green(gitBranch) + bg.dim(")") : bg.dim(" | ") + bg.white(cwd);
|
|
1828
|
-
const tools = toolStr ? bg.dim(" | ") + bg.dim(toolStr) : "";
|
|
1829
|
-
const right = bg.dim(" | ") + bg.dim(costStr) + bg.white(elapsed) + bg.dim(" ") + bg.green(modeStr) + " ";
|
|
1830
|
-
const stripAnsi = (s) => s.replace(/\x1b\[[0-9;]*m/g, "");
|
|
1831
|
-
const contentLen = stripAnsi(left).length + stripAnsi(middle).length + stripAnsi(tools).length + stripAnsi(right).length;
|
|
1832
|
-
const pad = Math.max(0, width - contentLen);
|
|
1833
|
-
return left + middle + tools + bg(" ".repeat(pad)) + right;
|
|
1834
|
-
}
|
|
1835
|
-
|
|
1836
1589
|
// src/cost-tracker.js
|
|
1837
1590
|
var PRICING = {
|
|
1838
|
-
// Anthropic
|
|
1591
|
+
// Anthropic (Claude 4.5/4.6)
|
|
1839
1592
|
"claude-opus-4-6": { input: 15, output: 75 },
|
|
1840
1593
|
"claude-sonnet-4-6": { input: 3, output: 15 },
|
|
1841
1594
|
"claude-haiku-4-5": { input: 0.8, output: 4 },
|
|
1842
|
-
// OpenAI
|
|
1843
|
-
"gpt-
|
|
1844
|
-
"gpt-
|
|
1845
|
-
"
|
|
1846
|
-
"
|
|
1847
|
-
// Google
|
|
1848
|
-
"gemini-
|
|
1849
|
-
"gemini-
|
|
1595
|
+
// OpenAI (GPT-5.x)
|
|
1596
|
+
"gpt-5.4": { input: 0.75, output: 4.5 },
|
|
1597
|
+
"gpt-5.4-mini": { input: 0.15, output: 0.6 },
|
|
1598
|
+
"gpt-5.4-pro": { input: 5, output: 20 },
|
|
1599
|
+
"gpt-5.3": { input: 1, output: 5 },
|
|
1600
|
+
// Google (Gemini 3.x)
|
|
1601
|
+
"gemini-3.1-pro": { input: 1.25, output: 10 },
|
|
1602
|
+
"gemini-3.1-flash-lite": { input: 0.15, output: 0.6 }
|
|
1603
|
+
// Ollama (free, local)
|
|
1850
1604
|
};
|
|
1851
1605
|
var CostTracker = class {
|
|
1852
1606
|
entries = [];
|
|
@@ -1886,21 +1640,13 @@ var CostTracker = class {
|
|
|
1886
1640
|
};
|
|
1887
1641
|
|
|
1888
1642
|
export {
|
|
1643
|
+
__dirname,
|
|
1889
1644
|
loadConfig,
|
|
1890
1645
|
saveConfig,
|
|
1891
1646
|
getProviderForPersona,
|
|
1647
|
+
buildOllamaOptions,
|
|
1892
1648
|
createModel,
|
|
1893
1649
|
killActiveProcess,
|
|
1894
1650
|
createToolDefinitions,
|
|
1895
|
-
PermissionManager,
|
|
1896
|
-
printHeader,
|
|
1897
|
-
printToolCall,
|
|
1898
|
-
getPersonaEmoji,
|
|
1899
|
-
printToolResult,
|
|
1900
|
-
wmLog,
|
|
1901
|
-
wmLogPrefix,
|
|
1902
|
-
wmCoordinatorLog,
|
|
1903
|
-
printError,
|
|
1904
|
-
printStatusBar,
|
|
1905
1651
|
CostTracker
|
|
1906
1652
|
};
|