@stan-chen/simple-cli 0.2.6 → 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 +312 -18
- package/dist/builtins.js +312 -6
- 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,10 +1,13 @@
|
|
|
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';
|
|
3
|
+
import { join, resolve, relative, extname, isAbsolute } from 'path';
|
|
4
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);
|
|
9
12
|
const activeProcesses = [];
|
|
10
13
|
export const cleanupProcesses = () => {
|
|
@@ -34,6 +37,13 @@ process.on('SIGTERM', () => {
|
|
|
34
37
|
cleanupProcesses();
|
|
35
38
|
process.exit();
|
|
36
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
|
+
};
|
|
37
47
|
export const readFiles = {
|
|
38
48
|
name: 'read_files',
|
|
39
49
|
description: 'Read contents of one or more files',
|
|
@@ -54,6 +64,10 @@ export const readFiles = {
|
|
|
54
64
|
const results = [];
|
|
55
65
|
for (const p of paths) {
|
|
56
66
|
try {
|
|
67
|
+
if (!isPathAllowed(p)) {
|
|
68
|
+
results.push({ path: p, error: "Access denied: Path is outside the allowed workspace." });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
57
71
|
if (existsSync(p)) {
|
|
58
72
|
const content = await readFile(p, 'utf-8');
|
|
59
73
|
results.push({ path: p, content });
|
|
@@ -113,6 +127,10 @@ export const writeFiles = {
|
|
|
113
127
|
results.push({ success: false, message: 'File path missing' });
|
|
114
128
|
continue;
|
|
115
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
|
+
}
|
|
116
134
|
const dir = resolve(f.path, '..');
|
|
117
135
|
if (!existsSync(dir)) {
|
|
118
136
|
await mkdir(dir, { recursive: true });
|
|
@@ -158,11 +176,29 @@ export const createTool = {
|
|
|
158
176
|
scope: z.enum(['local', 'global']).default('local')
|
|
159
177
|
}),
|
|
160
178
|
execute: async ({ source_path, name, description, usage, scope }) => {
|
|
179
|
+
if (!isPathAllowed(source_path))
|
|
180
|
+
return `Access denied: Path is outside the allowed workspace.`;
|
|
161
181
|
if (!existsSync(source_path))
|
|
162
182
|
return `Source file not found: ${source_path}`;
|
|
163
|
-
const content = await readFile(source_path, 'utf-8');
|
|
164
183
|
const ext = extname(source_path);
|
|
165
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');
|
|
166
202
|
let header = '';
|
|
167
203
|
if (ext === '.js' || ext === '.ts') {
|
|
168
204
|
header = `/**\n * ${name}\n * ${description}\n * Usage: ${usage}\n */\n\n`;
|
|
@@ -170,9 +206,6 @@ export const createTool = {
|
|
|
170
206
|
else if (ext === '.py') {
|
|
171
207
|
header = `"""\n${name}\n${description}\nUsage: ${usage}\n"""\n\n`;
|
|
172
208
|
}
|
|
173
|
-
const targetDir = scope === 'global'
|
|
174
|
-
? join(process.env.HOME || process.cwd(), '.agent', 'tools')
|
|
175
|
-
: join(process.cwd(), '.agent', 'tools');
|
|
176
209
|
await mkdir(targetDir, { recursive: true });
|
|
177
210
|
const targetPath = join(targetDir, filename);
|
|
178
211
|
await writeFile(targetPath, header + content);
|
|
@@ -238,6 +271,14 @@ export const listFiles = {
|
|
|
238
271
|
maxResults: z.number().optional()
|
|
239
272
|
}),
|
|
240
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
|
+
}
|
|
241
282
|
const files = await glob(pattern, {
|
|
242
283
|
cwd: path,
|
|
243
284
|
ignore: ignore,
|
|
@@ -267,6 +308,9 @@ export const searchFiles = {
|
|
|
267
308
|
filesOnly: z.boolean().default(false)
|
|
268
309
|
}),
|
|
269
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
|
+
}
|
|
270
314
|
let files = [];
|
|
271
315
|
try {
|
|
272
316
|
const stats = await stat(path);
|
|
@@ -353,6 +397,8 @@ export const listDir = {
|
|
|
353
397
|
description: 'List contents of a directory',
|
|
354
398
|
inputSchema: z.object({ path: z.string().default('.') }),
|
|
355
399
|
execute: async ({ path }) => {
|
|
400
|
+
if (!isPathAllowed(path))
|
|
401
|
+
return "Access denied: Path is outside the allowed workspace.";
|
|
356
402
|
const items = await readdir(path, { withFileTypes: true });
|
|
357
403
|
return items.map(i => ({ name: i.name, isDir: i.isDirectory() }));
|
|
358
404
|
}
|
|
@@ -429,6 +475,8 @@ export const deleteFile = {
|
|
|
429
475
|
const path = args.path || args.file || args.filename;
|
|
430
476
|
if (!path)
|
|
431
477
|
return "Error: 'path' argument required";
|
|
478
|
+
if (!isPathAllowed(path))
|
|
479
|
+
return "Access denied: Path is outside the allowed workspace.";
|
|
432
480
|
if (existsSync(path)) {
|
|
433
481
|
await unlink(path);
|
|
434
482
|
return `Deleted ${path}`;
|
|
@@ -446,6 +494,8 @@ export const gitTool = {
|
|
|
446
494
|
message: z.string().optional()
|
|
447
495
|
}),
|
|
448
496
|
execute: async ({ operation, cwd, files, message }) => {
|
|
497
|
+
if (!isPathAllowed(cwd))
|
|
498
|
+
return { success: false, error: "Access denied: Path is outside the allowed workspace." };
|
|
449
499
|
const run = async (cmd) => {
|
|
450
500
|
try {
|
|
451
501
|
const { stdout } = await execAsync(cmd, { cwd });
|
|
@@ -485,6 +535,8 @@ export const linter = {
|
|
|
485
535
|
description: 'Lint a file',
|
|
486
536
|
inputSchema: z.object({ path: z.string() }),
|
|
487
537
|
execute: async ({ path }) => {
|
|
538
|
+
if (!isPathAllowed(path))
|
|
539
|
+
return { passed: false, errors: [{ message: 'Access denied: Path is outside the allowed workspace.' }] };
|
|
488
540
|
if (!existsSync(path))
|
|
489
541
|
return { passed: false, errors: [{ message: 'File not found' }] };
|
|
490
542
|
const ext = extname(path);
|
|
@@ -549,4 +601,258 @@ export const getTrackedFiles = async (cwd) => {
|
|
|
549
601
|
return [];
|
|
550
602
|
}
|
|
551
603
|
};
|
|
552
|
-
|
|
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
|
+
}
|