@stan-chen/simple-cli 0.2.5 → 0.2.7
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 +77 -67
- package/dist/agents/jules.d.ts +21 -0
- package/dist/agents/jules.js +206 -0
- package/dist/agents/jules_client.d.ts +21 -0
- package/dist/agents/jules_client.js +158 -0
- package/dist/anyllm.py +6 -1
- package/dist/async_tasks.d.ts +20 -0
- package/dist/async_tasks.js +110 -0
- package/dist/builtins.d.ts +380 -20
- package/dist/builtins.js +387 -10
- package/dist/claw/jit.d.ts +5 -0
- package/dist/claw/jit.js +14 -0
- package/dist/cli.js +55 -4
- package/dist/config.d.ts +27 -0
- package/dist/config.js +21 -0
- package/dist/engine.js +79 -34
- package/dist/llm.d.ts +12 -6
- package/dist/llm.js +162 -56
- package/dist/mcp.js +3 -2
- package/dist/scheduler.d.ts +23 -0
- package/dist/scheduler.js +145 -0
- package/dist/swarm/remote_worker.d.ts +14 -0
- package/dist/swarm/remote_worker.js +9 -0
- package/dist/swarm/server.d.ts +17 -0
- package/dist/swarm/server.js +39 -0
- package/dist/tui.js +2 -7
- package/package.json +4 -4
- /package/{assets → docs/assets}/logo.jpeg +0 -0
package/dist/builtins.js
CHANGED
|
@@ -1,11 +1,49 @@
|
|
|
1
1
|
import { readFile, writeFile, readdir, unlink, mkdir, stat } from 'fs/promises';
|
|
2
2
|
import { existsSync } from 'fs';
|
|
3
|
-
import { join, resolve, relative, extname } from 'path';
|
|
4
|
-
import { exec } from 'child_process';
|
|
3
|
+
import { join, resolve, relative, extname, isAbsolute } from 'path';
|
|
4
|
+
import { exec, spawn, execSync } from 'child_process';
|
|
5
5
|
import { promisify } from 'util';
|
|
6
6
|
import { z } from 'zod';
|
|
7
7
|
import glob from 'fast-glob';
|
|
8
|
+
import { Scheduler } from './scheduler.js';
|
|
9
|
+
import { AsyncTaskManager } from './async_tasks.js';
|
|
10
|
+
import { loadConfig } from './config.js';
|
|
8
11
|
const execAsync = promisify(exec);
|
|
12
|
+
const activeProcesses = [];
|
|
13
|
+
export const cleanupProcesses = () => {
|
|
14
|
+
for (const proc of activeProcesses) {
|
|
15
|
+
if (!proc.killed && proc.pid) {
|
|
16
|
+
if (process.platform === 'win32') {
|
|
17
|
+
try {
|
|
18
|
+
execSync(`taskkill /F /T /PID ${proc.pid}`);
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
proc.kill('SIGTERM');
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
proc.kill('SIGTERM');
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
// Handle cleanup on exit
|
|
31
|
+
process.on('exit', cleanupProcesses);
|
|
32
|
+
process.on('SIGINT', () => {
|
|
33
|
+
cleanupProcesses();
|
|
34
|
+
process.exit();
|
|
35
|
+
});
|
|
36
|
+
process.on('SIGTERM', () => {
|
|
37
|
+
cleanupProcesses();
|
|
38
|
+
process.exit();
|
|
39
|
+
});
|
|
40
|
+
// Helper function to validate path is within allowed workspace
|
|
41
|
+
const isPathAllowed = (p) => {
|
|
42
|
+
const resolvedPath = resolve(p);
|
|
43
|
+
const workspaceRoot = resolve(process.cwd());
|
|
44
|
+
const relativePath = relative(workspaceRoot, resolvedPath);
|
|
45
|
+
return !relativePath.startsWith('..') && !isAbsolute(relativePath);
|
|
46
|
+
};
|
|
9
47
|
export const readFiles = {
|
|
10
48
|
name: 'read_files',
|
|
11
49
|
description: 'Read contents of one or more files',
|
|
@@ -26,6 +64,10 @@ export const readFiles = {
|
|
|
26
64
|
const results = [];
|
|
27
65
|
for (const p of paths) {
|
|
28
66
|
try {
|
|
67
|
+
if (!isPathAllowed(p)) {
|
|
68
|
+
results.push({ path: p, error: "Access denied: Path is outside the allowed workspace." });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
29
71
|
if (existsSync(p)) {
|
|
30
72
|
const content = await readFile(p, 'utf-8');
|
|
31
73
|
results.push({ path: p, content });
|
|
@@ -85,6 +127,10 @@ export const writeFiles = {
|
|
|
85
127
|
results.push({ success: false, message: 'File path missing' });
|
|
86
128
|
continue;
|
|
87
129
|
}
|
|
130
|
+
if (!isPathAllowed(f.path)) {
|
|
131
|
+
results.push({ path: f.path, success: false, message: "Access denied: Path is outside the allowed workspace." });
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
88
134
|
const dir = resolve(f.path, '..');
|
|
89
135
|
if (!existsSync(dir)) {
|
|
90
136
|
await mkdir(dir, { recursive: true });
|
|
@@ -130,11 +176,29 @@ export const createTool = {
|
|
|
130
176
|
scope: z.enum(['local', 'global']).default('local')
|
|
131
177
|
}),
|
|
132
178
|
execute: async ({ source_path, name, description, usage, scope }) => {
|
|
179
|
+
if (!isPathAllowed(source_path))
|
|
180
|
+
return `Access denied: Path is outside the allowed workspace.`;
|
|
133
181
|
if (!existsSync(source_path))
|
|
134
182
|
return `Source file not found: ${source_path}`;
|
|
135
|
-
const content = await readFile(source_path, 'utf-8');
|
|
136
183
|
const ext = extname(source_path);
|
|
137
184
|
const filename = `${name}${ext}`;
|
|
185
|
+
const targetDir = scope === 'global'
|
|
186
|
+
? join(process.env.HOME || process.cwd(), '.agent', 'tools')
|
|
187
|
+
: join(process.cwd(), '.agent', 'tools');
|
|
188
|
+
// Check for existing tools
|
|
189
|
+
if (existsSync(join(targetDir, filename))) {
|
|
190
|
+
return `Error: Tool '${name}' already exists at ${join(targetDir, filename)}. Please use a different name or delete the existing tool.`;
|
|
191
|
+
}
|
|
192
|
+
// Check for similar names (simple containment)
|
|
193
|
+
if (existsSync(targetDir)) {
|
|
194
|
+
const existing = await readdir(targetDir);
|
|
195
|
+
const similar = existing.find(f => f.startsWith(name) || name.startsWith(f.split('.')[0]));
|
|
196
|
+
if (similar) {
|
|
197
|
+
// Warning only, allow creation but notify
|
|
198
|
+
console.warn(`Warning: A similar tool '${similar}' already exists.`);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
const content = await readFile(source_path, 'utf-8');
|
|
138
202
|
let header = '';
|
|
139
203
|
if (ext === '.js' || ext === '.ts') {
|
|
140
204
|
header = `/**\n * ${name}\n * ${description}\n * Usage: ${usage}\n */\n\n`;
|
|
@@ -142,9 +206,6 @@ export const createTool = {
|
|
|
142
206
|
else if (ext === '.py') {
|
|
143
207
|
header = `"""\n${name}\n${description}\nUsage: ${usage}\n"""\n\n`;
|
|
144
208
|
}
|
|
145
|
-
const targetDir = scope === 'global'
|
|
146
|
-
? join(process.env.HOME || process.cwd(), '.agent', 'tools')
|
|
147
|
-
: join(process.cwd(), '.agent', 'tools');
|
|
148
209
|
await mkdir(targetDir, { recursive: true });
|
|
149
210
|
const targetPath = join(targetDir, filename);
|
|
150
211
|
await writeFile(targetPath, header + content);
|
|
@@ -210,6 +271,14 @@ export const listFiles = {
|
|
|
210
271
|
maxResults: z.number().optional()
|
|
211
272
|
}),
|
|
212
273
|
execute: async ({ pattern, path, ignore, includeDirectories, maxResults }) => {
|
|
274
|
+
if (!isPathAllowed(path)) {
|
|
275
|
+
return {
|
|
276
|
+
matches: [],
|
|
277
|
+
count: 0,
|
|
278
|
+
truncated: false,
|
|
279
|
+
error: "Access denied: Path is outside the allowed workspace."
|
|
280
|
+
};
|
|
281
|
+
}
|
|
213
282
|
const files = await glob(pattern, {
|
|
214
283
|
cwd: path,
|
|
215
284
|
ignore: ignore,
|
|
@@ -239,6 +308,9 @@ export const searchFiles = {
|
|
|
239
308
|
filesOnly: z.boolean().default(false)
|
|
240
309
|
}),
|
|
241
310
|
execute: async ({ pattern, path, glob: globPattern, ignoreCase, contextLines, maxResults, filesOnly }) => {
|
|
311
|
+
if (!isPathAllowed(path)) {
|
|
312
|
+
return { matches: [], count: 0, truncated: false, error: "Access denied: Path is outside the allowed workspace." };
|
|
313
|
+
}
|
|
242
314
|
let files = [];
|
|
243
315
|
try {
|
|
244
316
|
const stats = await stat(path);
|
|
@@ -325,15 +397,35 @@ export const listDir = {
|
|
|
325
397
|
description: 'List contents of a directory',
|
|
326
398
|
inputSchema: z.object({ path: z.string().default('.') }),
|
|
327
399
|
execute: async ({ path }) => {
|
|
400
|
+
if (!isPathAllowed(path))
|
|
401
|
+
return "Access denied: Path is outside the allowed workspace.";
|
|
328
402
|
const items = await readdir(path, { withFileTypes: true });
|
|
329
403
|
return items.map(i => ({ name: i.name, isDir: i.isDirectory() }));
|
|
330
404
|
}
|
|
331
405
|
};
|
|
332
406
|
export const runCommand = {
|
|
333
407
|
name: 'run_command',
|
|
334
|
-
description: 'Run a shell command',
|
|
335
|
-
inputSchema: z.object({
|
|
336
|
-
|
|
408
|
+
description: 'Run a shell command. Use background: true for servers or long-running tasks.',
|
|
409
|
+
inputSchema: z.object({
|
|
410
|
+
command: z.string(),
|
|
411
|
+
timeout: z.number().optional(),
|
|
412
|
+
background: z.boolean().default(false).describe('Run command in background and return immediately')
|
|
413
|
+
}),
|
|
414
|
+
execute: async ({ command, timeout, background }) => {
|
|
415
|
+
if (background) {
|
|
416
|
+
const child = spawn(command, {
|
|
417
|
+
shell: true,
|
|
418
|
+
detached: true,
|
|
419
|
+
stdio: 'ignore'
|
|
420
|
+
});
|
|
421
|
+
child.unref();
|
|
422
|
+
activeProcesses.push(child);
|
|
423
|
+
return {
|
|
424
|
+
message: `Started background process: ${command}`,
|
|
425
|
+
pid: child.pid,
|
|
426
|
+
success: true
|
|
427
|
+
};
|
|
428
|
+
}
|
|
337
429
|
try {
|
|
338
430
|
const { stdout, stderr } = await execAsync(command, { timeout });
|
|
339
431
|
return { stdout, stderr, exitCode: 0, timedOut: false };
|
|
@@ -350,6 +442,31 @@ export const runCommand = {
|
|
|
350
442
|
}
|
|
351
443
|
}
|
|
352
444
|
};
|
|
445
|
+
export const stopCommand = {
|
|
446
|
+
name: 'stop_command',
|
|
447
|
+
description: 'Stop a background process by its PID',
|
|
448
|
+
inputSchema: z.object({ pid: z.number() }),
|
|
449
|
+
execute: async ({ pid }) => {
|
|
450
|
+
if (process.platform === 'win32') {
|
|
451
|
+
try {
|
|
452
|
+
await execAsync(`taskkill /F /T /PID ${pid}`);
|
|
453
|
+
return `Successfully stopped process ${pid}`;
|
|
454
|
+
}
|
|
455
|
+
catch (e) {
|
|
456
|
+
return `Error stopping process ${pid}: ${e.message}`;
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
else {
|
|
460
|
+
try {
|
|
461
|
+
process.kill(pid, 'SIGTERM');
|
|
462
|
+
return `Sent SIGTERM to process ${pid}`;
|
|
463
|
+
}
|
|
464
|
+
catch (e) {
|
|
465
|
+
return `Error stopping process ${pid}: ${e.message}`;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
};
|
|
353
470
|
export const deleteFile = {
|
|
354
471
|
name: 'delete_file',
|
|
355
472
|
description: 'Delete a file',
|
|
@@ -358,6 +475,8 @@ export const deleteFile = {
|
|
|
358
475
|
const path = args.path || args.file || args.filename;
|
|
359
476
|
if (!path)
|
|
360
477
|
return "Error: 'path' argument required";
|
|
478
|
+
if (!isPathAllowed(path))
|
|
479
|
+
return "Access denied: Path is outside the allowed workspace.";
|
|
361
480
|
if (existsSync(path)) {
|
|
362
481
|
await unlink(path);
|
|
363
482
|
return `Deleted ${path}`;
|
|
@@ -375,6 +494,8 @@ export const gitTool = {
|
|
|
375
494
|
message: z.string().optional()
|
|
376
495
|
}),
|
|
377
496
|
execute: async ({ operation, cwd, files, message }) => {
|
|
497
|
+
if (!isPathAllowed(cwd))
|
|
498
|
+
return { success: false, error: "Access denied: Path is outside the allowed workspace." };
|
|
378
499
|
const run = async (cmd) => {
|
|
379
500
|
try {
|
|
380
501
|
const { stdout } = await execAsync(cmd, { cwd });
|
|
@@ -414,6 +535,8 @@ export const linter = {
|
|
|
414
535
|
description: 'Lint a file',
|
|
415
536
|
inputSchema: z.object({ path: z.string() }),
|
|
416
537
|
execute: async ({ path }) => {
|
|
538
|
+
if (!isPathAllowed(path))
|
|
539
|
+
return { passed: false, errors: [{ message: 'Access denied: Path is outside the allowed workspace.' }] };
|
|
417
540
|
if (!existsSync(path))
|
|
418
541
|
return { passed: false, errors: [{ message: 'File not found' }] };
|
|
419
542
|
const ext = extname(path);
|
|
@@ -478,4 +601,258 @@ export const getTrackedFiles = async (cwd) => {
|
|
|
478
601
|
return [];
|
|
479
602
|
}
|
|
480
603
|
};
|
|
481
|
-
|
|
604
|
+
// --- Meta-Orchestrator Tools ---
|
|
605
|
+
export const delegate_cli = {
|
|
606
|
+
name: 'delegate_cli',
|
|
607
|
+
description: 'Delegate a complex task to a specialized external CLI agent (Codex, Gemini, Claude).',
|
|
608
|
+
inputSchema: z.object({
|
|
609
|
+
cli: z.string(),
|
|
610
|
+
task: z.string(),
|
|
611
|
+
context_files: z.array(z.string()).optional(),
|
|
612
|
+
async: z.boolean().default(false).describe("Run in background mode. Returns a Task ID to monitor.")
|
|
613
|
+
}),
|
|
614
|
+
execute: async ({ cli, task, context_files, async }) => {
|
|
615
|
+
try {
|
|
616
|
+
console.log(`[delegate_cli] Spawning external process for ${cli}...`);
|
|
617
|
+
const config = await loadConfig();
|
|
618
|
+
// Default to mock if no config for this agent
|
|
619
|
+
if (!config.agents || !config.agents[cli]) {
|
|
620
|
+
console.warn(`[delegate_cli] No configuration found for '${cli}'. Falling back to mock.`);
|
|
621
|
+
const cmd = `npx tsx tests/manual_scripts/mock_cli.ts "${task}"`;
|
|
622
|
+
const { stdout, stderr } = await execAsync(cmd);
|
|
623
|
+
if (stderr)
|
|
624
|
+
console.warn(`[delegate_cli] Stderr: ${stderr}`);
|
|
625
|
+
return `[${cli} CLI (Mock)]:\n${stdout.trim()}`;
|
|
626
|
+
}
|
|
627
|
+
const agent = config.agents[cli];
|
|
628
|
+
const cmdArgs = [...(agent.args || []), task];
|
|
629
|
+
// Handle file arguments for agents that don't support stdin or use --file flags
|
|
630
|
+
if (!agent.supports_stdin && context_files && context_files.length > 0) {
|
|
631
|
+
for (const file of context_files) {
|
|
632
|
+
if (!isPathAllowed(file)) {
|
|
633
|
+
console.warn(`[delegate_cli] Skipped restricted file: ${file}`);
|
|
634
|
+
continue;
|
|
635
|
+
}
|
|
636
|
+
const flag = agent.context_flag !== undefined ? agent.context_flag : '--file';
|
|
637
|
+
if (flag) {
|
|
638
|
+
cmdArgs.push(flag, file);
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
cmdArgs.push(file);
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
const child = spawn(agent.command, cmdArgs, {
|
|
646
|
+
env: { ...process.env, ...agent.env },
|
|
647
|
+
shell: false // Use false for safer arg handling, unless command relies on shell features
|
|
648
|
+
});
|
|
649
|
+
// Async Mode Handling
|
|
650
|
+
if (async) {
|
|
651
|
+
// Determine command to run for AsyncTaskManager
|
|
652
|
+
// We need to reconstruct the full command string for AsyncTaskManager or pass args
|
|
653
|
+
// Actually AsyncTaskManager takes command and args.
|
|
654
|
+
// But we already spawned 'child' here?
|
|
655
|
+
// Wait, if async, we shouldn't spawn here and wait.
|
|
656
|
+
// We should delegate the spawning to AsyncTaskManager.
|
|
657
|
+
// Let's refactor:
|
|
658
|
+
child.kill(); // Kill the one we just started (oops, inefficient but safe if we didn't write stdin yet)
|
|
659
|
+
// Actually, let's just use AsyncTaskManager INSTEAD of spawning manually.
|
|
660
|
+
const taskManager = AsyncTaskManager.getInstance();
|
|
661
|
+
// Prepare context file content to pass as environment variable or temp file
|
|
662
|
+
// if the agent expects stdin.
|
|
663
|
+
// AsyncTaskManager runs detached, so we can't easily pipe stdin unless we wrap it.
|
|
664
|
+
// For simplicity, if async is requested, we might only support 'context_files' passed as args.
|
|
665
|
+
// If agent requires stdin, we can write a temporary input file and pipe it:
|
|
666
|
+
// cmd: "cat input.txt | agent ..."
|
|
667
|
+
// This is getting complex for 'generic' agents.
|
|
668
|
+
// Strategy: Just run the agent command. If it needs context files, they are in cmdArgs.
|
|
669
|
+
// If it needs stdin, we warn or skip for now in async mode unless we implement 'input_file' support.
|
|
670
|
+
if (agent.supports_stdin && context_files && context_files.length > 0) {
|
|
671
|
+
return `[delegate_cli] Warning: Async mode with Stdin context is not fully supported yet. Please use 'async: false' or ensure agent accepts files via arguments.`;
|
|
672
|
+
}
|
|
673
|
+
const id = await taskManager.startTask(agent.command, cmdArgs, agent.env);
|
|
674
|
+
return `[delegate_cli] Async Task Started.\nID: ${id}\nMonitor status using 'check_task_status'.`;
|
|
675
|
+
}
|
|
676
|
+
// Sync Mode (Existing Logic)
|
|
677
|
+
// Handle Stdin Context Injection
|
|
678
|
+
if (agent.supports_stdin && context_files && context_files.length > 0) {
|
|
679
|
+
// Read files and pipe to stdin
|
|
680
|
+
// Format: --- filename ---\n content \n ---
|
|
681
|
+
let context = "";
|
|
682
|
+
for (const file of context_files) {
|
|
683
|
+
if (!isPathAllowed(file)) {
|
|
684
|
+
console.warn(`[delegate_cli] Skipped restricted file: ${file}`);
|
|
685
|
+
continue;
|
|
686
|
+
}
|
|
687
|
+
if (existsSync(file)) {
|
|
688
|
+
const content = await readFile(file, 'utf-8');
|
|
689
|
+
context += `--- ${file} ---\n${content}\n\n`;
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
child.stdin.write(context);
|
|
693
|
+
child.stdin.end();
|
|
694
|
+
}
|
|
695
|
+
else {
|
|
696
|
+
child.stdin.end();
|
|
697
|
+
}
|
|
698
|
+
let stdout = '';
|
|
699
|
+
let stderr = '';
|
|
700
|
+
child.stdout.on('data', d => stdout += d.toString());
|
|
701
|
+
child.stderr.on('data', d => stderr += d.toString());
|
|
702
|
+
return new Promise((resolve, reject) => {
|
|
703
|
+
child.on('close', code => {
|
|
704
|
+
if (stderr)
|
|
705
|
+
console.warn(`[delegate_cli] Stderr: ${stderr}`);
|
|
706
|
+
if (code === 0) {
|
|
707
|
+
resolve(`[${cli} CLI]:\n${stdout.trim()}`);
|
|
708
|
+
}
|
|
709
|
+
else {
|
|
710
|
+
resolve(`[${cli} CLI] Process exited with code ${code}.\nOutput: ${stdout}\nError: ${stderr}`);
|
|
711
|
+
}
|
|
712
|
+
});
|
|
713
|
+
child.on('error', (err) => {
|
|
714
|
+
resolve(`[${cli} CLI] Failed to start process: ${err.message}`);
|
|
715
|
+
});
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
catch (e) {
|
|
719
|
+
return `[${cli} CLI]: Error executing external process: ${e.message}`;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
};
|
|
723
|
+
export const schedule_task = {
|
|
724
|
+
name: 'schedule_task',
|
|
725
|
+
description: 'Register a recurring task to be executed by the agent autonomously.',
|
|
726
|
+
inputSchema: z.object({
|
|
727
|
+
cron: z.string().describe('Standard cron expression (e.g. "0 9 * * *")'),
|
|
728
|
+
prompt: z.string().describe('The instruction to execute'),
|
|
729
|
+
description: z.string().describe('Human-readable description')
|
|
730
|
+
}),
|
|
731
|
+
execute: async ({ cron, prompt, description }) => {
|
|
732
|
+
try {
|
|
733
|
+
const scheduler = Scheduler.getInstance();
|
|
734
|
+
const id = await scheduler.scheduleTask(cron, prompt, description);
|
|
735
|
+
return `Task scheduled successfully with ID: ${id}`;
|
|
736
|
+
}
|
|
737
|
+
catch (e) {
|
|
738
|
+
return `Failed to schedule task: ${e.message}`;
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
};
|
|
742
|
+
export const pr_list = {
|
|
743
|
+
name: 'pr_list',
|
|
744
|
+
description: 'List open pull requests',
|
|
745
|
+
inputSchema: z.object({ limit: z.number().default(10) }),
|
|
746
|
+
execute: async ({ limit }) => {
|
|
747
|
+
try {
|
|
748
|
+
const { stdout } = await execAsync(`gh pr list --limit ${limit}`);
|
|
749
|
+
return stdout.trim() || 'No open PRs found.';
|
|
750
|
+
}
|
|
751
|
+
catch (e) {
|
|
752
|
+
return `Error listing PRs: ${e.message}`;
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
};
|
|
756
|
+
export const pr_review = {
|
|
757
|
+
name: 'pr_review',
|
|
758
|
+
description: 'Get the diff/details of a PR',
|
|
759
|
+
inputSchema: z.object({ pr_number: z.number() }),
|
|
760
|
+
execute: async ({ pr_number }) => {
|
|
761
|
+
try {
|
|
762
|
+
const { stdout } = await execAsync(`gh pr diff ${pr_number}`);
|
|
763
|
+
return stdout.trim();
|
|
764
|
+
}
|
|
765
|
+
catch (e) {
|
|
766
|
+
return `Error reviewing PR ${pr_number}: ${e.message}`;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
};
|
|
770
|
+
export const pr_comment = {
|
|
771
|
+
name: 'pr_comment',
|
|
772
|
+
description: 'Add a comment to a PR (e.g., to instruct Jules to fix something).',
|
|
773
|
+
inputSchema: z.object({
|
|
774
|
+
pr_number: z.number(),
|
|
775
|
+
body: z.string()
|
|
776
|
+
}),
|
|
777
|
+
execute: async ({ pr_number, body }) => {
|
|
778
|
+
try {
|
|
779
|
+
await execAsync(`gh pr comment ${pr_number} --body "${body}"`);
|
|
780
|
+
return `Commented on PR ${pr_number}: "${body}"`;
|
|
781
|
+
}
|
|
782
|
+
catch (e) {
|
|
783
|
+
return `Error commenting on PR ${pr_number}: ${e.message}`;
|
|
784
|
+
}
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
export const pr_ready = {
|
|
788
|
+
name: 'pr_ready',
|
|
789
|
+
description: 'Mark a Draft PR as Ready for Review',
|
|
790
|
+
inputSchema: z.object({ pr_number: z.number() }),
|
|
791
|
+
execute: async ({ pr_number }) => {
|
|
792
|
+
try {
|
|
793
|
+
await execAsync(`gh pr ready ${pr_number}`);
|
|
794
|
+
return `PR ${pr_number} marked as Ready for Review`;
|
|
795
|
+
}
|
|
796
|
+
catch (e) {
|
|
797
|
+
return `Error marking PR ${pr_number} as ready: ${e.message}`;
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
};
|
|
801
|
+
export const pr_merge = {
|
|
802
|
+
name: 'pr_merge',
|
|
803
|
+
description: 'Merge a pull request',
|
|
804
|
+
inputSchema: z.object({
|
|
805
|
+
pr_number: z.number(),
|
|
806
|
+
method: z.enum(['merge', 'squash', 'rebase']).default('merge')
|
|
807
|
+
}),
|
|
808
|
+
execute: async ({ pr_number, method }) => {
|
|
809
|
+
try {
|
|
810
|
+
await execAsync(`gh pr merge ${pr_number} --${method} --delete-branch`);
|
|
811
|
+
return `Successfully merged PR ${pr_number}`;
|
|
812
|
+
}
|
|
813
|
+
catch (e) {
|
|
814
|
+
return `Error merging PR ${pr_number}: ${e.message}`;
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
};
|
|
818
|
+
export const check_task_status = {
|
|
819
|
+
name: 'check_task_status',
|
|
820
|
+
description: 'Check the status and logs of a background task.',
|
|
821
|
+
inputSchema: z.object({
|
|
822
|
+
id: z.string().describe('The Task ID returned by delegate_cli')
|
|
823
|
+
}),
|
|
824
|
+
execute: async ({ id }) => {
|
|
825
|
+
try {
|
|
826
|
+
const manager = AsyncTaskManager.getInstance();
|
|
827
|
+
const task = await manager.getTaskStatus(id);
|
|
828
|
+
const logs = await manager.getTaskLogs(id, 5); // Last 5 lines
|
|
829
|
+
return `Task ID: ${task.id}
|
|
830
|
+
Status: ${task.status}
|
|
831
|
+
PID: ${task.pid}
|
|
832
|
+
Last Logs:
|
|
833
|
+
${logs}
|
|
834
|
+
`;
|
|
835
|
+
}
|
|
836
|
+
catch (e) {
|
|
837
|
+
return `Error checking task ${id}: ${e.message}`;
|
|
838
|
+
}
|
|
839
|
+
}
|
|
840
|
+
};
|
|
841
|
+
export const list_bg_tasks = {
|
|
842
|
+
name: 'list_bg_tasks',
|
|
843
|
+
description: 'List all background tasks.',
|
|
844
|
+
inputSchema: z.object({}),
|
|
845
|
+
execute: async () => {
|
|
846
|
+
try {
|
|
847
|
+
const manager = AsyncTaskManager.getInstance();
|
|
848
|
+
const tasks = await manager.listTasks();
|
|
849
|
+
if (tasks.length === 0)
|
|
850
|
+
return "No background tasks found.";
|
|
851
|
+
return tasks.map(t => `- [${t.status.toUpperCase()}] ${t.id}: ${t.command} (Started: ${new Date(t.startTime).toISOString()})`).join('\n');
|
|
852
|
+
}
|
|
853
|
+
catch (e) {
|
|
854
|
+
return `Error listing tasks: ${e.message}`;
|
|
855
|
+
}
|
|
856
|
+
}
|
|
857
|
+
};
|
|
858
|
+
export const allBuiltins = [readFiles, writeFiles, createTool, scrapeUrl, listFiles, searchFiles, listDir, runCommand, stopCommand, deleteFile, gitTool, linter, delegate_cli, schedule_task, check_task_status, list_bg_tasks, pr_list, pr_review, pr_comment, pr_ready, pr_merge];
|
package/dist/claw/jit.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import * as path from 'path';
|
|
3
|
+
/**
|
|
4
|
+
* [Simple-CLI AI-Created]
|
|
5
|
+
* Recreated file based on task description.
|
|
6
|
+
*/
|
|
7
|
+
export async function buildMemoryContext(reflectionsDir, files) {
|
|
8
|
+
// Optimized: Concurrent non-blocking reads using Promise.all
|
|
9
|
+
const contents = await Promise.all(files.map(async (f) => {
|
|
10
|
+
const content = await readFile(path.join(reflectionsDir, f), 'utf-8');
|
|
11
|
+
return `\n--- Previous Reflection (${f}) ---\n${content}\n`;
|
|
12
|
+
}));
|
|
13
|
+
return contents.join('');
|
|
14
|
+
}
|
package/dist/cli.js
CHANGED
|
@@ -7,17 +7,25 @@ import { createLLM } from './llm.js';
|
|
|
7
7
|
import { MCP } from './mcp.js';
|
|
8
8
|
import { getActiveSkill } from './skills.js';
|
|
9
9
|
import { showBanner } from './tui.js';
|
|
10
|
+
import { Scheduler } from './scheduler.js';
|
|
11
|
+
import { log, outro } from '@clack/prompts';
|
|
10
12
|
async function main() {
|
|
11
13
|
const args = process.argv.slice(2);
|
|
12
14
|
// Handle optional directory argument
|
|
13
15
|
let cwd = process.cwd();
|
|
14
16
|
let interactive = true;
|
|
17
|
+
let daemon = false;
|
|
15
18
|
const remainingArgs = [];
|
|
16
19
|
for (const arg of args) {
|
|
17
20
|
if (arg === '--non-interactive') {
|
|
18
21
|
interactive = false;
|
|
19
22
|
continue;
|
|
20
23
|
}
|
|
24
|
+
if (arg === '--daemon') {
|
|
25
|
+
daemon = true;
|
|
26
|
+
interactive = false;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
21
29
|
if (!arg.startsWith('-')) {
|
|
22
30
|
try {
|
|
23
31
|
if (statSync(arg).isDirectory()) {
|
|
@@ -37,9 +45,52 @@ async function main() {
|
|
|
37
45
|
const mcp = new MCP();
|
|
38
46
|
const provider = createLLM();
|
|
39
47
|
const engine = new Engine(provider, registry, mcp);
|
|
40
|
-
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
48
|
+
// --- Scheduler Integration ---
|
|
49
|
+
const scheduler = Scheduler.getInstance(cwd);
|
|
50
|
+
const processDueTasks = async () => {
|
|
51
|
+
const dueTasks = await scheduler.getDueTasks();
|
|
52
|
+
if (dueTasks.length > 0) {
|
|
53
|
+
log.info(`Found ${dueTasks.length} scheduled tasks due.`);
|
|
54
|
+
for (const task of dueTasks) {
|
|
55
|
+
log.step(`Running task: ${task.description} (${task.cron})`);
|
|
56
|
+
// Use a fresh context for each task
|
|
57
|
+
const taskCtx = new Context(cwd, await getActiveSkill(cwd));
|
|
58
|
+
try {
|
|
59
|
+
// Run non-interactively
|
|
60
|
+
await engine.run(taskCtx, task.prompt, { interactive: false });
|
|
61
|
+
await scheduler.markTaskRun(task.id, true);
|
|
62
|
+
log.success(`Task ${task.id} completed.`);
|
|
63
|
+
}
|
|
64
|
+
catch (e) {
|
|
65
|
+
log.error(`Task ${task.id} failed: ${e.message}`);
|
|
66
|
+
await scheduler.markTaskRun(task.id, false);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
log.info('All scheduled tasks processed.');
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
if (daemon) {
|
|
73
|
+
showBanner();
|
|
74
|
+
log.info('Running in daemon mode. Checking for tasks every 60s...');
|
|
75
|
+
while (true) {
|
|
76
|
+
try {
|
|
77
|
+
await processDueTasks();
|
|
78
|
+
}
|
|
79
|
+
catch (e) {
|
|
80
|
+
log.error(`Error in daemon loop: ${e.message}`);
|
|
81
|
+
}
|
|
82
|
+
// Sleep for 60 seconds
|
|
83
|
+
await new Promise(resolve => setTimeout(resolve, 60000));
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
showBanner();
|
|
88
|
+
// Standard run: check tasks once, then interactive
|
|
89
|
+
await processDueTasks();
|
|
90
|
+
const skill = await getActiveSkill(cwd);
|
|
91
|
+
const ctx = new Context(cwd, skill);
|
|
92
|
+
await engine.run(ctx, prompt || undefined, { interactive });
|
|
93
|
+
outro('Session finished.');
|
|
94
|
+
}
|
|
44
95
|
}
|
|
45
96
|
main().catch(console.error);
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface AgentConfig {
|
|
2
|
+
command: string;
|
|
3
|
+
args: string[];
|
|
4
|
+
description: string;
|
|
5
|
+
supports_stdin?: boolean;
|
|
6
|
+
env?: Record<string, string>;
|
|
7
|
+
context_flag?: string;
|
|
8
|
+
}
|
|
9
|
+
export interface TaskConfig {
|
|
10
|
+
id: string;
|
|
11
|
+
cron: string;
|
|
12
|
+
command: string;
|
|
13
|
+
description: string;
|
|
14
|
+
enabled?: boolean;
|
|
15
|
+
}
|
|
16
|
+
export interface SchedulerConfig {
|
|
17
|
+
enabled: boolean;
|
|
18
|
+
tasks: TaskConfig[];
|
|
19
|
+
}
|
|
20
|
+
export interface Config {
|
|
21
|
+
mcpServers?: Record<string, any>;
|
|
22
|
+
agents?: Record<string, AgentConfig>;
|
|
23
|
+
scheduler?: SchedulerConfig;
|
|
24
|
+
yoloMode?: boolean;
|
|
25
|
+
autoDecisionTimeout?: number;
|
|
26
|
+
}
|
|
27
|
+
export declare function loadConfig(cwd?: string): Promise<Config>;
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { readFile } from 'fs/promises';
|
|
2
|
+
import { existsSync } from 'fs';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
export async function loadConfig(cwd = process.cwd()) {
|
|
5
|
+
const locations = [
|
|
6
|
+
join(cwd, 'mcp.json'),
|
|
7
|
+
join(cwd, '.agent', 'config.json')
|
|
8
|
+
];
|
|
9
|
+
for (const loc of locations) {
|
|
10
|
+
if (existsSync(loc)) {
|
|
11
|
+
try {
|
|
12
|
+
const content = await readFile(loc, 'utf-8');
|
|
13
|
+
return JSON.parse(content);
|
|
14
|
+
}
|
|
15
|
+
catch (e) {
|
|
16
|
+
console.error(`Failed to parse config at ${loc}:`, e);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return {};
|
|
21
|
+
}
|