homarus 0.1.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.
Files changed (83) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +114 -0
  3. package/dist/agent-manager.d.ts +35 -0
  4. package/dist/agent-manager.js +127 -0
  5. package/dist/agent-worker.d.ts +2 -0
  6. package/dist/agent-worker.js +141 -0
  7. package/dist/agent.d.ts +33 -0
  8. package/dist/agent.js +197 -0
  9. package/dist/browser-manager.d.ts +33 -0
  10. package/dist/browser-manager.js +170 -0
  11. package/dist/channel-adapter.d.ts +29 -0
  12. package/dist/channel-adapter.js +94 -0
  13. package/dist/channel-manager.d.ts +17 -0
  14. package/dist/channel-manager.js +84 -0
  15. package/dist/cli.d.ts +3 -0
  16. package/dist/cli.js +212 -0
  17. package/dist/config.d.ts +21 -0
  18. package/dist/config.js +185 -0
  19. package/dist/embedding-provider.d.ts +35 -0
  20. package/dist/embedding-provider.js +103 -0
  21. package/dist/event-bus.d.ts +18 -0
  22. package/dist/event-bus.js +46 -0
  23. package/dist/event-queue.d.ts +18 -0
  24. package/dist/event-queue.js +77 -0
  25. package/dist/execution-strategy.d.ts +26 -0
  26. package/dist/execution-strategy.js +20 -0
  27. package/dist/homarus.d.ts +36 -0
  28. package/dist/homarus.js +308 -0
  29. package/dist/http-api.d.ts +16 -0
  30. package/dist/http-api.js +82 -0
  31. package/dist/identity-manager.d.ts +28 -0
  32. package/dist/identity-manager.js +123 -0
  33. package/dist/memory-index.d.ts +52 -0
  34. package/dist/memory-index.js +286 -0
  35. package/dist/model-provider.d.ts +33 -0
  36. package/dist/model-provider.js +255 -0
  37. package/dist/model-router.d.ts +32 -0
  38. package/dist/model-router.js +148 -0
  39. package/dist/setup-wizard.d.ts +28 -0
  40. package/dist/setup-wizard.js +240 -0
  41. package/dist/skill-manager.d.ts +26 -0
  42. package/dist/skill-manager.js +171 -0
  43. package/dist/skill-transport.d.ts +51 -0
  44. package/dist/skill-transport.js +116 -0
  45. package/dist/skill.d.ts +22 -0
  46. package/dist/skill.js +118 -0
  47. package/dist/subprocess-strategy.d.ts +54 -0
  48. package/dist/subprocess-strategy.js +106 -0
  49. package/dist/telegram-adapter.d.ts +34 -0
  50. package/dist/telegram-adapter.js +165 -0
  51. package/dist/timer-service.d.ts +30 -0
  52. package/dist/timer-service.js +142 -0
  53. package/dist/tool-registry.d.ts +29 -0
  54. package/dist/tool-registry.js +100 -0
  55. package/dist/tools/bash.d.ts +3 -0
  56. package/dist/tools/bash.js +48 -0
  57. package/dist/tools/browser.d.ts +4 -0
  58. package/dist/tools/browser.js +47 -0
  59. package/dist/tools/edit.d.ts +3 -0
  60. package/dist/tools/edit.js +48 -0
  61. package/dist/tools/git.d.ts +3 -0
  62. package/dist/tools/git.js +109 -0
  63. package/dist/tools/glob.d.ts +3 -0
  64. package/dist/tools/glob.js +86 -0
  65. package/dist/tools/grep.d.ts +3 -0
  66. package/dist/tools/grep.js +169 -0
  67. package/dist/tools/index.d.ts +6 -0
  68. package/dist/tools/index.js +46 -0
  69. package/dist/tools/lsp.d.ts +3 -0
  70. package/dist/tools/lsp.js +216 -0
  71. package/dist/tools/memory.d.ts +4 -0
  72. package/dist/tools/memory.js +64 -0
  73. package/dist/tools/read.d.ts +3 -0
  74. package/dist/tools/read.js +49 -0
  75. package/dist/tools/web-fetch.d.ts +3 -0
  76. package/dist/tools/web-fetch.js +51 -0
  77. package/dist/tools/web-search.d.ts +3 -0
  78. package/dist/tools/web-search.js +73 -0
  79. package/dist/tools/write.d.ts +3 -0
  80. package/dist/tools/write.js +31 -0
  81. package/dist/types.d.ts +240 -0
  82. package/dist/types.js +14 -0
  83. package/package.json +69 -0
@@ -0,0 +1,142 @@
1
+ // CRC: crc-TimerService.md | Seq: seq-startup.md, seq-timer-fire.md
2
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "node:fs";
3
+ import { dirname } from "node:path";
4
+ import { v4 as uuid } from "uuid";
5
+ import { Cron } from "croner";
6
+ export class TimerService {
7
+ timers = new Map();
8
+ storePath;
9
+ logger;
10
+ emitFn = null;
11
+ constructor(logger, storePath) {
12
+ this.logger = logger;
13
+ this.storePath = storePath;
14
+ }
15
+ setEmitter(fn) {
16
+ this.emitFn = fn;
17
+ }
18
+ // CRC: crc-TimerService.md — loadTimers()
19
+ loadTimers() {
20
+ if (!existsSync(this.storePath))
21
+ return;
22
+ try {
23
+ const data = JSON.parse(readFileSync(this.storePath, "utf-8"));
24
+ for (const config of data) {
25
+ this.timers.set(config.id ?? uuid(), { config });
26
+ }
27
+ this.logger.info("Loaded timers", { count: this.timers.size });
28
+ }
29
+ catch (err) {
30
+ this.logger.error("Failed to load timers", { error: String(err) });
31
+ }
32
+ }
33
+ // CRC: crc-TimerService.md — saveTimers()
34
+ saveTimers() {
35
+ const data = [...this.timers.values()].map((e) => e.config);
36
+ const dir = dirname(this.storePath);
37
+ if (!existsSync(dir))
38
+ mkdirSync(dir, { recursive: true });
39
+ writeFileSync(this.storePath, JSON.stringify(data, null, 2));
40
+ }
41
+ // CRC: crc-TimerService.md — add()
42
+ add(config) {
43
+ const id = config.id ?? uuid();
44
+ config.id = id;
45
+ this.timers.set(id, { config });
46
+ this.startTimer(id);
47
+ this.saveTimers();
48
+ this.logger.info("Timer added", { id, name: config.name, type: config.type });
49
+ return id;
50
+ }
51
+ // CRC: crc-TimerService.md — remove()
52
+ remove(timerId) {
53
+ const entry = this.timers.get(timerId);
54
+ if (!entry)
55
+ return;
56
+ this.stopTimer(entry);
57
+ this.timers.delete(timerId);
58
+ this.saveTimers();
59
+ this.logger.info("Timer removed", { id: timerId });
60
+ }
61
+ // CRC: crc-TimerService.md — get()
62
+ get(timerId) {
63
+ return this.timers.get(timerId)?.config;
64
+ }
65
+ // CRC: crc-TimerService.md — getAll()
66
+ getAll() {
67
+ return [...this.timers.values()].map((e) => e.config);
68
+ }
69
+ // CRC: crc-TimerService.md — start()
70
+ start() {
71
+ for (const [id] of this.timers) {
72
+ this.startTimer(id);
73
+ }
74
+ this.logger.info("Timer service started", { count: this.timers.size });
75
+ }
76
+ // CRC: crc-TimerService.md — stop()
77
+ stop() {
78
+ for (const entry of this.timers.values()) {
79
+ this.stopTimer(entry);
80
+ }
81
+ this.saveTimers();
82
+ this.logger.info("Timer service stopped");
83
+ }
84
+ // CRC: crc-TimerService.md — onFire()
85
+ onFire(timerId) {
86
+ const entry = this.timers.get(timerId);
87
+ if (!entry || !this.emitFn)
88
+ return;
89
+ const event = {
90
+ id: uuid(),
91
+ type: "timer_fired",
92
+ source: `timer:${timerId}`,
93
+ timestamp: Date.now(),
94
+ payload: {
95
+ timerId,
96
+ name: entry.config.name,
97
+ prompt: entry.config.prompt,
98
+ model: entry.config.model,
99
+ },
100
+ };
101
+ this.logger.info("Timer fired", { id: timerId, name: entry.config.name });
102
+ this.emitFn(event);
103
+ // Auto-remove one-shot timers
104
+ if (entry.config.type === "once") {
105
+ this.timers.delete(timerId);
106
+ this.saveTimers();
107
+ }
108
+ }
109
+ startTimer(id) {
110
+ const entry = this.timers.get(id);
111
+ if (!entry)
112
+ return;
113
+ this.stopTimer(entry);
114
+ const { config } = entry;
115
+ switch (config.type) {
116
+ case "cron":
117
+ entry.job = new Cron(config.schedule, { timezone: config.timezone }, () => this.onFire(id));
118
+ break;
119
+ case "interval":
120
+ entry.timeout = setInterval(() => this.onFire(id), parseInt(config.schedule, 10));
121
+ break;
122
+ case "once": {
123
+ const fireAt = new Date(config.schedule).getTime();
124
+ const delay = Math.max(0, fireAt - Date.now());
125
+ entry.timeout = setTimeout(() => this.onFire(id), delay);
126
+ break;
127
+ }
128
+ }
129
+ }
130
+ stopTimer(entry) {
131
+ if (entry.job) {
132
+ entry.job.stop();
133
+ entry.job = undefined;
134
+ }
135
+ if (entry.timeout) {
136
+ clearTimeout(entry.timeout);
137
+ clearInterval(entry.timeout);
138
+ entry.timeout = undefined;
139
+ }
140
+ }
141
+ }
142
+ //# sourceMappingURL=timer-service.js.map
@@ -0,0 +1,29 @@
1
+ import type { ToolDefinition, ToolResult, ToolContext, Logger } from "./types.js";
2
+ export interface ToolPolicy {
3
+ name: string;
4
+ allow?: string[];
5
+ deny?: string[];
6
+ }
7
+ export declare class ToolRegistry {
8
+ private tools;
9
+ private policies;
10
+ private groups;
11
+ private logger;
12
+ constructor(logger: Logger);
13
+ register(tool: ToolDefinition): void;
14
+ unregister(name: string): void;
15
+ get(name: string): ToolDefinition | undefined;
16
+ getAll(): ToolDefinition[];
17
+ getForAgent(allowedTools?: string[]): ToolDefinition[];
18
+ execute(name: string, params: unknown, context: ToolContext): Promise<ToolResult>;
19
+ registerGroup(name: string, toolNames: string[]): void;
20
+ resolveGroup(groupName: string): string[];
21
+ addPolicy(policy: ToolPolicy): void;
22
+ checkPolicy(toolName: string, _context: ToolContext): boolean;
23
+ toSchemas(): Array<{
24
+ name: string;
25
+ description: string;
26
+ parameters: Record<string, unknown>;
27
+ }>;
28
+ }
29
+ //# sourceMappingURL=tool-registry.d.ts.map
@@ -0,0 +1,100 @@
1
+ const BUILTIN_GROUPS = {
2
+ "group:fs": ["read", "write", "edit", "glob", "grep"],
3
+ "group:runtime": ["bash", "git"],
4
+ "group:web": ["web_fetch", "web_search", "browser"],
5
+ "group:code": ["lsp"],
6
+ "group:memory": ["memory_search", "memory_get", "memory_store"],
7
+ };
8
+ export class ToolRegistry {
9
+ tools = new Map();
10
+ policies = [];
11
+ groups = new Map(Object.entries(BUILTIN_GROUPS));
12
+ logger;
13
+ constructor(logger) {
14
+ this.logger = logger;
15
+ }
16
+ // CRC: crc-ToolRegistry.md — register()
17
+ register(tool) {
18
+ this.tools.set(tool.name, tool);
19
+ this.logger.debug("Registered tool", { name: tool.name, source: tool.source });
20
+ }
21
+ // CRC: crc-ToolRegistry.md — unregister()
22
+ unregister(name) {
23
+ this.tools.delete(name);
24
+ }
25
+ // CRC: crc-ToolRegistry.md — get()
26
+ get(name) {
27
+ return this.tools.get(name);
28
+ }
29
+ // CRC: crc-ToolRegistry.md — getAll()
30
+ getAll() {
31
+ return [...this.tools.values()];
32
+ }
33
+ // CRC: crc-ToolRegistry.md — getForAgent()
34
+ getForAgent(allowedTools) {
35
+ const all = this.getAll();
36
+ if (!allowedTools)
37
+ return all;
38
+ // Resolve groups in the allow list
39
+ const resolved = new Set();
40
+ for (const name of allowedTools) {
41
+ const group = this.groups.get(name);
42
+ if (group) {
43
+ group.forEach((t) => resolved.add(t));
44
+ }
45
+ else {
46
+ resolved.add(name);
47
+ }
48
+ }
49
+ return all.filter((t) => resolved.has(t.name));
50
+ }
51
+ // CRC: crc-ToolRegistry.md — execute()
52
+ async execute(name, params, context) {
53
+ const tool = this.tools.get(name);
54
+ if (!tool) {
55
+ return { output: "", error: `Unknown tool: ${name}` };
56
+ }
57
+ if (!this.checkPolicy(name, context)) {
58
+ return { output: "", error: `Tool ${name} denied by policy` };
59
+ }
60
+ const start = Date.now();
61
+ try {
62
+ const result = await tool.execute(params, context);
63
+ this.logger.debug("Tool executed", { name, durationMs: Date.now() - start });
64
+ return result;
65
+ }
66
+ catch (err) {
67
+ this.logger.error("Tool execution failed", { name, error: String(err) });
68
+ return { output: "", error: String(err) };
69
+ }
70
+ }
71
+ // CRC: crc-ToolRegistry.md — registerGroup()
72
+ registerGroup(name, toolNames) {
73
+ this.groups.set(name, toolNames);
74
+ }
75
+ // CRC: crc-ToolRegistry.md — resolveGroup()
76
+ resolveGroup(groupName) {
77
+ return this.groups.get(groupName) ?? [];
78
+ }
79
+ addPolicy(policy) {
80
+ this.policies.push(policy);
81
+ }
82
+ // CRC: crc-ToolRegistry.md — checkPolicy()
83
+ checkPolicy(toolName, _context) {
84
+ for (const policy of this.policies) {
85
+ if (policy.deny?.includes(toolName))
86
+ return false;
87
+ if (policy.allow && !policy.allow.includes(toolName))
88
+ return false;
89
+ }
90
+ return true;
91
+ }
92
+ toSchemas() {
93
+ return this.getAll().map((t) => ({
94
+ name: t.name,
95
+ description: t.description,
96
+ parameters: t.parameters,
97
+ }));
98
+ }
99
+ }
100
+ //# sourceMappingURL=tool-registry.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ export declare const bashTool: ToolDefinition;
3
+ //# sourceMappingURL=bash.d.ts.map
@@ -0,0 +1,48 @@
1
+ // Built-in tool: bash — execute shell commands
2
+ import { exec } from "node:child_process";
3
+ const MAX_OUTPUT = 50_000; // chars
4
+ export const bashTool = {
5
+ name: "bash",
6
+ description: "Execute a bash command and return stdout/stderr. Use for system commands, git operations, running scripts, installing packages, etc.",
7
+ parameters: {
8
+ type: "object",
9
+ properties: {
10
+ command: { type: "string", description: "The bash command to execute" },
11
+ timeout: { type: "number", description: "Timeout in milliseconds (default 120000)" },
12
+ workingDir: { type: "string", description: "Working directory for the command" },
13
+ },
14
+ required: ["command"],
15
+ },
16
+ source: "builtin",
17
+ async execute(params, context) {
18
+ const { command, timeout = 120_000, workingDir } = params;
19
+ if (context.sandbox) {
20
+ return { output: "", error: "bash tool is not available in sandbox mode" };
21
+ }
22
+ return new Promise((resolve) => {
23
+ const proc = exec(command, {
24
+ cwd: workingDir ?? context.workingDir,
25
+ timeout,
26
+ maxBuffer: 10 * 1024 * 1024, // 10MB
27
+ env: { ...process.env, TERM: "dumb" },
28
+ }, (error, stdout, stderr) => {
29
+ let output = stdout;
30
+ if (stderr)
31
+ output += (output ? "\n" : "") + stderr;
32
+ if (output.length > MAX_OUTPUT) {
33
+ output = output.slice(0, MAX_OUTPUT) + `\n... (truncated, ${output.length} total chars)`;
34
+ }
35
+ if (error && error.killed) {
36
+ resolve({ output, error: `Command timed out after ${timeout}ms` });
37
+ }
38
+ else if (error) {
39
+ resolve({ output, error: `Exit code ${error.code}: ${error.message}` });
40
+ }
41
+ else {
42
+ resolve({ output });
43
+ }
44
+ });
45
+ });
46
+ },
47
+ };
48
+ //# sourceMappingURL=bash.js.map
@@ -0,0 +1,4 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ import type { BrowserManager } from "../browser-manager.js";
3
+ export declare function createBrowserTool(browserManager: BrowserManager): ToolDefinition;
4
+ //# sourceMappingURL=browser.d.ts.map
@@ -0,0 +1,47 @@
1
+ export function createBrowserTool(browserManager) {
2
+ return {
3
+ name: "browser",
4
+ description: `Control a headless browser. Actions: navigate (go to URL), click (click element by CSS selector), type (fill input by selector), screenshot (capture page as base64 PNG), content (get visible page text), evaluate (run JavaScript), back/forward (navigation history), wait (wait for selector or duration), scroll (scroll up/down).`,
5
+ parameters: {
6
+ type: "object",
7
+ properties: {
8
+ action: {
9
+ type: "string",
10
+ description: "The browser action to perform",
11
+ enum: ["navigate", "click", "type", "screenshot", "content", "evaluate", "back", "forward", "wait", "scroll"],
12
+ },
13
+ url: { type: "string", description: "URL to navigate to (for navigate action)" },
14
+ selector: { type: "string", description: "CSS selector for the target element (for click, type, wait)" },
15
+ text: { type: "string", description: "Text to type (for type action)" },
16
+ script: { type: "string", description: "JavaScript to evaluate (for evaluate action)" },
17
+ timeout: { type: "number", description: "Timeout in milliseconds" },
18
+ direction: { type: "string", description: "Scroll direction (for scroll action)", enum: ["up", "down"] },
19
+ pixels: { type: "number", description: "Pixels to scroll (for scroll action, default 500)" },
20
+ },
21
+ required: ["action"],
22
+ },
23
+ source: "builtin",
24
+ async execute(params, context) {
25
+ if (context.sandbox) {
26
+ return { output: "", error: "browser tool is not available in sandbox mode" };
27
+ }
28
+ const action = params;
29
+ const result = await browserManager.execute(action);
30
+ if (!result.success) {
31
+ return { output: "", error: result.error ?? "Browser action failed" };
32
+ }
33
+ // Build output with context
34
+ const parts = [];
35
+ if (result.url)
36
+ parts.push(`URL: ${result.url}`);
37
+ if (result.title)
38
+ parts.push(`Title: ${result.title}`);
39
+ if (result.data)
40
+ parts.push(result.data);
41
+ if (result.screenshot)
42
+ parts.push(`[Screenshot: ${result.screenshot.length} chars base64]`);
43
+ return { output: parts.join("\n") };
44
+ },
45
+ };
46
+ }
47
+ //# sourceMappingURL=browser.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ export declare const editTool: ToolDefinition;
3
+ //# sourceMappingURL=edit.d.ts.map
@@ -0,0 +1,48 @@
1
+ // Built-in tool: edit — find and replace text in a file
2
+ import { readFileSync, writeFileSync, existsSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+ export const editTool = {
5
+ name: "edit",
6
+ description: "Perform an exact string replacement in a file. The old_string must be unique in the file unless replace_all is true.",
7
+ parameters: {
8
+ type: "object",
9
+ properties: {
10
+ path: { type: "string", description: "Absolute or relative path to the file" },
11
+ old_string: { type: "string", description: "The exact text to find and replace" },
12
+ new_string: { type: "string", description: "The replacement text" },
13
+ replace_all: { type: "boolean", description: "Replace all occurrences (default false)" },
14
+ },
15
+ required: ["path", "old_string", "new_string"],
16
+ },
17
+ source: "builtin",
18
+ async execute(params, context) {
19
+ const { path: filePath, old_string, new_string, replace_all = false } = params;
20
+ const absPath = resolve(context.workingDir, filePath);
21
+ if (context.sandbox) {
22
+ return { output: "", error: "edit tool is not available in sandbox mode" };
23
+ }
24
+ if (!existsSync(absPath)) {
25
+ return { output: "", error: `File not found: ${absPath}` };
26
+ }
27
+ const content = readFileSync(absPath, "utf-8");
28
+ if (!content.includes(old_string)) {
29
+ return { output: "", error: "old_string not found in file" };
30
+ }
31
+ if (!replace_all) {
32
+ const firstIdx = content.indexOf(old_string);
33
+ const secondIdx = content.indexOf(old_string, firstIdx + 1);
34
+ if (secondIdx !== -1) {
35
+ return { output: "", error: "old_string is not unique in the file. Provide more context or use replace_all." };
36
+ }
37
+ }
38
+ const updated = replace_all
39
+ ? content.split(old_string).join(new_string)
40
+ : content.replace(old_string, new_string);
41
+ writeFileSync(absPath, updated);
42
+ const count = replace_all
43
+ ? content.split(old_string).length - 1
44
+ : 1;
45
+ return { output: `Replaced ${count} occurrence(s) in ${absPath}` };
46
+ },
47
+ };
48
+ //# sourceMappingURL=edit.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ export declare const gitTool: ToolDefinition;
3
+ //# sourceMappingURL=git.d.ts.map
@@ -0,0 +1,109 @@
1
+ // Built-in tool: git — safe git operations with guardrails
2
+ import { exec } from "node:child_process";
3
+ // Actions that modify state — require confirmation context
4
+ const WRITE_ACTIONS = new Set([
5
+ "add", "commit", "checkout", "pull", "push", "fetch",
6
+ "stash", "stash_pop", "merge", "rebase", "tag", "init", "clone",
7
+ ]);
8
+ // Dangerous patterns that are always blocked
9
+ const BLOCKED_PATTERNS = [
10
+ /--force\b/,
11
+ /push\s+--force/,
12
+ /reset\s+--hard/,
13
+ /clean\s+-f/,
14
+ /branch\s+-D/,
15
+ /--no-verify/,
16
+ ];
17
+ function buildCommand(action, args, message) {
18
+ switch (action) {
19
+ case "status": return `git status${args ? " " + args : ""}`;
20
+ case "diff": return `git diff${args ? " " + args : ""}`;
21
+ case "log": return `git log --oneline -20${args ? " " + args : ""}`;
22
+ case "show": return `git show${args ? " " + args : " HEAD"}`;
23
+ case "branch": return `git branch${args ? " " + args : ""}`;
24
+ case "branches": return "git branch -a";
25
+ case "add": return `git add ${args ?? "."}`;
26
+ case "commit": {
27
+ if (!message)
28
+ return "echo 'Error: commit requires a message'";
29
+ return `git commit -m ${JSON.stringify(message)}`;
30
+ }
31
+ case "checkout": return `git checkout ${args ?? ""}`;
32
+ case "pull": return `git pull${args ? " " + args : ""}`;
33
+ case "push": return `git push${args ? " " + args : ""}`;
34
+ case "fetch": return `git fetch${args ? " " + args : " --all"}`;
35
+ case "stash": return `git stash${args ? " " + args : ""}`;
36
+ case "stash_pop": return "git stash pop";
37
+ case "merge": return `git merge ${args ?? ""}`;
38
+ case "rebase": return `git rebase ${args ?? ""}`;
39
+ case "blame": return `git blame ${args ?? ""}`;
40
+ case "tag": return `git tag ${args ?? ""}`;
41
+ case "tags": return "git tag -l";
42
+ case "remote": return `git remote ${args ?? "-v"}`;
43
+ case "init": return "git init";
44
+ case "clone": return `git clone ${args ?? ""}`;
45
+ default: return `git ${action}${args ? " " + args : ""}`;
46
+ }
47
+ }
48
+ export const gitTool = {
49
+ name: "git",
50
+ description: `Git operations with safety guardrails. Blocks force-push, hard reset, and destructive commands. Actions: status, diff, log, show, branch, branches, add, commit (requires message), checkout, pull, push, fetch, stash, stash_pop, merge, rebase, blame, tag, tags, remote, init, clone.`,
51
+ parameters: {
52
+ type: "object",
53
+ properties: {
54
+ action: {
55
+ type: "string",
56
+ description: "The git action to perform",
57
+ enum: [
58
+ "status", "diff", "log", "show", "branch", "branches",
59
+ "add", "commit", "checkout", "pull", "push", "fetch",
60
+ "stash", "stash_pop", "merge", "rebase",
61
+ "blame", "tag", "tags", "remote", "init", "clone",
62
+ ],
63
+ },
64
+ args: { type: "string", description: "Additional arguments (e.g. file paths, branch names, flags)" },
65
+ message: { type: "string", description: "Commit message (required for commit action)" },
66
+ path: { type: "string", description: "Working directory for the git command" },
67
+ },
68
+ required: ["action"],
69
+ },
70
+ source: "builtin",
71
+ async execute(params, context) {
72
+ const { action, args, message, path } = params;
73
+ if (context.sandbox && WRITE_ACTIONS.has(action)) {
74
+ return { output: "", error: `git ${action} is not available in sandbox mode` };
75
+ }
76
+ const command = buildCommand(action, args, message);
77
+ // Safety check — block dangerous patterns
78
+ for (const pattern of BLOCKED_PATTERNS) {
79
+ if (pattern.test(command)) {
80
+ return { output: "", error: `Blocked: "${command}" matches dangerous pattern. Use bash tool directly if you really need this.` };
81
+ }
82
+ }
83
+ const cwd = path ? path : context.workingDir;
84
+ return new Promise((resolve) => {
85
+ exec(command, {
86
+ cwd,
87
+ timeout: 60_000,
88
+ maxBuffer: 5 * 1024 * 1024,
89
+ env: { ...process.env, GIT_TERMINAL_PROMPT: "0" },
90
+ }, (error, stdout, stderr) => {
91
+ let output = stdout;
92
+ if (stderr) {
93
+ // Git uses stderr for progress info — include it
94
+ output += (output ? "\n" : "") + stderr;
95
+ }
96
+ if (output.length > 50_000) {
97
+ output = output.slice(0, 50_000) + "\n... (truncated)";
98
+ }
99
+ if (error) {
100
+ resolve({ output, error: `git ${action} failed (exit ${error.code}): ${error.message}` });
101
+ }
102
+ else {
103
+ resolve({ output });
104
+ }
105
+ });
106
+ });
107
+ },
108
+ };
109
+ //# sourceMappingURL=git.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ export declare const globTool: ToolDefinition;
3
+ //# sourceMappingURL=glob.d.ts.map
@@ -0,0 +1,86 @@
1
+ // Built-in tool: glob — find files by pattern
2
+ import { resolve } from "node:path";
3
+ import { readdirSync, statSync, existsSync } from "node:fs";
4
+ const MAX_RESULTS = 500;
5
+ function minimatch(filePath, pattern) {
6
+ // Convert glob pattern to regex
7
+ let regex = pattern
8
+ .replace(/\./g, "\\.")
9
+ .replace(/\*\*/g, "⭐⭐") // placeholder
10
+ .replace(/\*/g, "[^/]*")
11
+ .replace(/⭐⭐/g, ".*")
12
+ .replace(/\?/g, "[^/]");
13
+ if (!regex.startsWith(".*") && !regex.startsWith("/")) {
14
+ regex = "(^|/)" + regex;
15
+ }
16
+ return new RegExp(regex + "$").test(filePath);
17
+ }
18
+ function walkDir(dir, pattern, results, maxDepth = 20, depth = 0) {
19
+ if (depth > maxDepth || results.length >= MAX_RESULTS)
20
+ return;
21
+ let entries;
22
+ try {
23
+ entries = readdirSync(dir, { withFileTypes: true });
24
+ }
25
+ catch {
26
+ return; // Permission denied or other read error
27
+ }
28
+ for (const entry of entries) {
29
+ if (results.length >= MAX_RESULTS)
30
+ return;
31
+ if (entry.name.startsWith(".") && !pattern.startsWith("."))
32
+ continue;
33
+ if (entry.name === "node_modules" || entry.name === ".git")
34
+ continue;
35
+ const fullPath = resolve(dir, entry.name);
36
+ if (entry.isDirectory()) {
37
+ walkDir(fullPath, pattern, results, maxDepth, depth + 1);
38
+ }
39
+ else if (entry.isFile()) {
40
+ // Match against relative path from search root
41
+ if (minimatch(fullPath, pattern) || minimatch(entry.name, pattern)) {
42
+ results.push(fullPath);
43
+ }
44
+ }
45
+ }
46
+ }
47
+ export const globTool = {
48
+ name: "glob",
49
+ description: 'Find files matching a glob pattern. Supports ** (any depth), * (single level), ? (single char). Excludes node_modules and .git. Examples: "**/*.ts", "src/**/test-*", "*.md"',
50
+ parameters: {
51
+ type: "object",
52
+ properties: {
53
+ pattern: { type: "string", description: 'Glob pattern to match files (e.g. "**/*.ts", "src/**/*.test.ts")' },
54
+ path: { type: "string", description: "Directory to search in (default: working directory)" },
55
+ },
56
+ required: ["pattern"],
57
+ },
58
+ source: "builtin",
59
+ async execute(params, context) {
60
+ const { pattern, path: searchPath } = params;
61
+ const rootDir = searchPath ? resolve(context.workingDir, searchPath) : context.workingDir;
62
+ if (!existsSync(rootDir)) {
63
+ return { output: "", error: `Directory not found: ${rootDir}` };
64
+ }
65
+ const results = [];
66
+ walkDir(rootDir, pattern, results);
67
+ // Sort by modification time (newest first)
68
+ results.sort((a, b) => {
69
+ try {
70
+ return statSync(b).mtimeMs - statSync(a).mtimeMs;
71
+ }
72
+ catch {
73
+ return 0;
74
+ }
75
+ });
76
+ if (results.length === 0) {
77
+ return { output: "No files found" };
78
+ }
79
+ let output = results.join("\n");
80
+ if (results.length >= MAX_RESULTS) {
81
+ output += `\n... (truncated at ${MAX_RESULTS} results)`;
82
+ }
83
+ return { output };
84
+ },
85
+ };
86
+ //# sourceMappingURL=glob.js.map
@@ -0,0 +1,3 @@
1
+ import type { ToolDefinition } from "../types.js";
2
+ export declare const grepTool: ToolDefinition;
3
+ //# sourceMappingURL=grep.d.ts.map