@axplusb/kepler 0.0.1 → 1.0.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 +82 -0
- package/package.json +36 -4
- package/pulse/app/activity/page.tsx +190 -0
- package/pulse/app/api/activity/route.ts +138 -0
- package/pulse/app/api/costs/route.ts +88 -0
- package/pulse/app/api/export/route.ts +77 -0
- package/pulse/app/api/history/route.ts +11 -0
- package/pulse/app/api/import/route.ts +31 -0
- package/pulse/app/api/memory/route.ts +52 -0
- package/pulse/app/api/plans/route.ts +9 -0
- package/pulse/app/api/projects/[slug]/route.ts +96 -0
- package/pulse/app/api/projects/route.ts +121 -0
- package/pulse/app/api/sessions/[id]/replay/route.ts +20 -0
- package/pulse/app/api/sessions/[id]/route.ts +31 -0
- package/pulse/app/api/sessions/route.ts +112 -0
- package/pulse/app/api/settings/route.ts +14 -0
- package/pulse/app/api/stats/route.ts +143 -0
- package/pulse/app/api/todos/route.ts +9 -0
- package/pulse/app/api/tools/route.ts +160 -0
- package/pulse/app/costs/page.tsx +179 -0
- package/pulse/app/export/page.tsx +465 -0
- package/pulse/app/favicon.ico +0 -0
- package/pulse/app/globals.css +263 -0
- package/pulse/app/help/page.tsx +142 -0
- package/pulse/app/history/page.tsx +157 -0
- package/pulse/app/layout.tsx +46 -0
- package/pulse/app/memory/page.tsx +365 -0
- package/pulse/app/overview-client.tsx +393 -0
- package/pulse/app/page.tsx +14 -0
- package/pulse/app/plans/page.tsx +308 -0
- package/pulse/app/projects/[slug]/page.tsx +390 -0
- package/pulse/app/projects/page.tsx +110 -0
- package/pulse/app/sessions/[id]/page.tsx +243 -0
- package/pulse/app/sessions/page.tsx +39 -0
- package/pulse/app/settings/page.tsx +188 -0
- package/pulse/app/todos/page.tsx +211 -0
- package/pulse/app/tools/page.tsx +249 -0
- package/pulse/cli.js +159 -0
- package/pulse/components/activity/day-of-week-chart.tsx +35 -0
- package/pulse/components/activity/streak-card.tsx +36 -0
- package/pulse/components/costs/cache-efficiency-panel.tsx +76 -0
- package/pulse/components/costs/cost-by-project-chart.tsx +48 -0
- package/pulse/components/costs/cost-over-time-chart.tsx +95 -0
- package/pulse/components/costs/model-token-table.tsx +60 -0
- package/pulse/components/global-search.tsx +193 -0
- package/pulse/components/keyboard-nav-provider.tsx +23 -0
- package/pulse/components/layout/bottom-nav.tsx +52 -0
- package/pulse/components/layout/client-layout.tsx +31 -0
- package/pulse/components/layout/sidebar-context.tsx +50 -0
- package/pulse/components/layout/sidebar.tsx +182 -0
- package/pulse/components/layout/top-bar.tsx +121 -0
- package/pulse/components/overview/activity-heatmap.tsx +107 -0
- package/pulse/components/overview/conversation-table.tsx +148 -0
- package/pulse/components/overview/model-breakdown-donut.tsx +95 -0
- package/pulse/components/overview/peak-hours-chart.tsx +87 -0
- package/pulse/components/overview/project-activity-donut.tsx +96 -0
- package/pulse/components/overview/stat-card.tsx +102 -0
- package/pulse/components/overview/usage-over-time-chart.tsx +166 -0
- package/pulse/components/projects/project-card.tsx +175 -0
- package/pulse/components/sessions/replay/assistant-markdown.tsx +94 -0
- package/pulse/components/sessions/replay/compaction-card.tsx +25 -0
- package/pulse/components/sessions/replay/session-sidebar.tsx +231 -0
- package/pulse/components/sessions/replay/token-accumulation-chart.tsx +98 -0
- package/pulse/components/sessions/replay/tool-call-badge.tsx +127 -0
- package/pulse/components/sessions/replay/turn-cards.tsx +220 -0
- package/pulse/components/sessions/replay/user-tool-result.tsx +158 -0
- package/pulse/components/sessions/session-badges.tsx +49 -0
- package/pulse/components/sessions/session-table.tsx +299 -0
- package/pulse/components/theme-provider.tsx +44 -0
- package/pulse/components/tools/feature-adoption-table.tsx +58 -0
- package/pulse/components/tools/mcp-server-panel.tsx +45 -0
- package/pulse/components/tools/tool-ranking-chart.tsx +57 -0
- package/pulse/components/tools/version-history-table.tsx +32 -0
- package/pulse/components/ui/alert.tsx +66 -0
- package/pulse/components/ui/badge.tsx +48 -0
- package/pulse/components/ui/breadcrumb.tsx +109 -0
- package/pulse/components/ui/button.tsx +64 -0
- package/pulse/components/ui/calendar.tsx +220 -0
- package/pulse/components/ui/card.tsx +92 -0
- package/pulse/components/ui/command.tsx +158 -0
- package/pulse/components/ui/dialog.tsx +158 -0
- package/pulse/components/ui/input.tsx +21 -0
- package/pulse/components/ui/popover.tsx +89 -0
- package/pulse/components/ui/progress.tsx +31 -0
- package/pulse/components/ui/select.tsx +190 -0
- package/pulse/components/ui/separator.tsx +28 -0
- package/pulse/components/ui/sheet.tsx +143 -0
- package/pulse/components/ui/skeleton.tsx +13 -0
- package/pulse/components/ui/table.tsx +116 -0
- package/pulse/components/ui/tabs.tsx +91 -0
- package/pulse/components/ui/tooltip.tsx +57 -0
- package/pulse/components/use-global-keyboard-nav.ts +79 -0
- package/pulse/components.json +23 -0
- package/pulse/eslint.config.mjs +18 -0
- package/pulse/lib/claude-reader.ts +594 -0
- package/pulse/lib/decode.ts +129 -0
- package/pulse/lib/pricing.ts +102 -0
- package/pulse/lib/replay-parser.ts +165 -0
- package/pulse/lib/tool-categories.ts +127 -0
- package/pulse/lib/utils.ts +6 -0
- package/pulse/next-env.d.ts +6 -0
- package/pulse/next.config.ts +16 -0
- package/pulse/package.json +45 -0
- package/pulse/postcss.config.mjs +7 -0
- package/pulse/public/activity.png +0 -0
- package/pulse/public/cc-lens.png +0 -0
- package/pulse/public/command-k.png +0 -0
- package/pulse/public/costs.png +0 -0
- package/pulse/public/dashboard-dark.png +0 -0
- package/pulse/public/dashboard-white.png +0 -0
- package/pulse/public/export.png +0 -0
- package/pulse/public/file.svg +1 -0
- package/pulse/public/globe.svg +1 -0
- package/pulse/public/next.svg +1 -0
- package/pulse/public/projects.png +0 -0
- package/pulse/public/session-chat.png +0 -0
- package/pulse/public/todos.png +0 -0
- package/pulse/public/tools.png +0 -0
- package/pulse/public/vercel.svg +1 -0
- package/pulse/public/window.svg +1 -0
- package/pulse/tsconfig.json +34 -0
- package/pulse/types/claude.ts +294 -0
- package/src/agents/loader.mjs +89 -0
- package/src/agents/parser.mjs +98 -0
- package/src/agents/teams.mjs +123 -0
- package/src/auth/oauth.mjs +220 -0
- package/src/auth/tarang-auth.mjs +277 -0
- package/src/config/cli-args.mjs +173 -0
- package/src/config/env.mjs +263 -0
- package/src/config/settings.mjs +132 -0
- package/src/context/ast-parser.mjs +298 -0
- package/src/context/bm25.mjs +85 -0
- package/src/context/retriever.mjs +270 -0
- package/src/context/skeleton.mjs +134 -0
- package/src/core/agent-loop.mjs +480 -0
- package/src/core/approval.mjs +273 -0
- package/src/core/backend-url.mjs +57 -0
- package/src/core/cache.mjs +105 -0
- package/src/core/callback-client.mjs +149 -0
- package/src/core/checkpoints.mjs +142 -0
- package/src/core/context-manager.mjs +198 -0
- package/src/core/headless.mjs +168 -0
- package/src/core/hooks-manager.mjs +87 -0
- package/src/core/jsonl-writer.mjs +351 -0
- package/src/core/local-agent.mjs +429 -0
- package/src/core/local-store.mjs +325 -0
- package/src/core/mode-selector.mjs +51 -0
- package/src/core/output-filter.mjs +177 -0
- package/src/core/paths.mjs +98 -0
- package/src/core/pricing.mjs +314 -0
- package/src/core/providers.mjs +219 -0
- package/src/core/rate-limiter.mjs +119 -0
- package/src/core/safety.mjs +200 -0
- package/src/core/scheduler.mjs +173 -0
- package/src/core/session-manager.mjs +317 -0
- package/src/core/session.mjs +143 -0
- package/src/core/settings-sync.mjs +85 -0
- package/src/core/stagnation.mjs +57 -0
- package/src/core/stream-client.mjs +367 -0
- package/src/core/streaming.mjs +182 -0
- package/src/core/system-prompt.mjs +135 -0
- package/src/core/tool-executor.mjs +725 -0
- package/src/hooks/engine.mjs +162 -0
- package/src/index.mjs +370 -0
- package/src/mcp/client.mjs +253 -0
- package/src/mcp/transport-shttp.mjs +130 -0
- package/src/mcp/transport-sse.mjs +131 -0
- package/src/mcp/transport-ws.mjs +134 -0
- package/src/permissions/checker.mjs +57 -0
- package/src/permissions/command-classifier.mjs +573 -0
- package/src/permissions/injection-check.mjs +60 -0
- package/src/permissions/path-check.mjs +102 -0
- package/src/permissions/prompt.mjs +73 -0
- package/src/permissions/sandbox.mjs +112 -0
- package/src/plugins/loader.mjs +138 -0
- package/src/skills/loader.mjs +147 -0
- package/src/skills/runner.mjs +55 -0
- package/src/telemetry/index.mjs +96 -0
- package/src/terminal/agents.mjs +177 -0
- package/src/terminal/analytics.mjs +292 -0
- package/src/terminal/ansi.mjs +421 -0
- package/src/terminal/main.mjs +150 -0
- package/src/terminal/repl.mjs +1484 -0
- package/src/terminal/tool-display.mjs +58 -0
- package/src/tools/agent.mjs +137 -0
- package/src/tools/ask-user.mjs +61 -0
- package/src/tools/bash.mjs +148 -0
- package/src/tools/cron-create.mjs +120 -0
- package/src/tools/cron-delete.mjs +49 -0
- package/src/tools/cron-list.mjs +37 -0
- package/src/tools/edit.mjs +82 -0
- package/src/tools/enter-worktree.mjs +69 -0
- package/src/tools/exit-worktree.mjs +57 -0
- package/src/tools/glob.mjs +117 -0
- package/src/tools/grep.mjs +129 -0
- package/src/tools/lint.mjs +71 -0
- package/src/tools/ls.mjs +58 -0
- package/src/tools/lsp.mjs +115 -0
- package/src/tools/multi-edit.mjs +94 -0
- package/src/tools/notebook-edit.mjs +96 -0
- package/src/tools/read-mcp-resource.mjs +57 -0
- package/src/tools/read.mjs +138 -0
- package/src/tools/registry.mjs +132 -0
- package/src/tools/remote-trigger.mjs +84 -0
- package/src/tools/send-message.mjs +64 -0
- package/src/tools/skill.mjs +52 -0
- package/src/tools/test-runner.mjs +49 -0
- package/src/tools/todo-write.mjs +68 -0
- package/src/tools/tool-search.mjs +77 -0
- package/src/tools/web-fetch.mjs +65 -0
- package/src/tools/web-search.mjs +89 -0
- package/src/tools/write.mjs +55 -0
- package/src/ui/banner.mjs +237 -0
- package/src/ui/commands.mjs +499 -0
- package/src/ui/formatter.mjs +379 -0
- package/src/ui/markdown.mjs +278 -0
- package/src/ui/slash-commands.mjs +258 -0
- package/index.js +0 -1
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EnterWorktree Tool — create and enter a git worktree for isolation.
|
|
3
|
+
*
|
|
4
|
+
* Creates a temporary worktree branch so edits do not affect the main branch.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { execSync } from 'child_process';
|
|
8
|
+
import path from 'path';
|
|
9
|
+
import os from 'os';
|
|
10
|
+
|
|
11
|
+
export const EnterWorktreeTool = {
|
|
12
|
+
name: 'EnterWorktree',
|
|
13
|
+
description: 'Create a git worktree for isolated file editing.',
|
|
14
|
+
inputSchema: {
|
|
15
|
+
type: 'object',
|
|
16
|
+
properties: {
|
|
17
|
+
branch: {
|
|
18
|
+
type: 'string',
|
|
19
|
+
description: 'Branch name for the worktree (auto-generated if omitted)',
|
|
20
|
+
},
|
|
21
|
+
path: {
|
|
22
|
+
type: 'string',
|
|
23
|
+
description: 'Directory for the worktree (temp dir if omitted)',
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
required: [],
|
|
27
|
+
},
|
|
28
|
+
|
|
29
|
+
// Shared state for active worktree
|
|
30
|
+
_activeWorktree: null,
|
|
31
|
+
|
|
32
|
+
validateInput() { return []; },
|
|
33
|
+
|
|
34
|
+
async call(input) {
|
|
35
|
+
if (this._activeWorktree) {
|
|
36
|
+
return `Already in worktree at ${this._activeWorktree.path}. Use ExitWorktree first.`;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
try {
|
|
40
|
+
// Verify we are in a git repo
|
|
41
|
+
execSync('git rev-parse --is-inside-work-tree', { encoding: 'utf-8' });
|
|
42
|
+
} catch {
|
|
43
|
+
return 'Error: not inside a git repository';
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
const branch = input.branch || `occ-worktree-${Date.now()}`;
|
|
47
|
+
const worktreePath = input.path || path.join(os.tmpdir(), `occ-wt-${Date.now()}`);
|
|
48
|
+
const originalCwd = process.cwd();
|
|
49
|
+
|
|
50
|
+
try {
|
|
51
|
+
execSync(`git worktree add -b "${branch}" "${worktreePath}"`, {
|
|
52
|
+
encoding: 'utf-8',
|
|
53
|
+
stdio: 'pipe',
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
this._activeWorktree = {
|
|
57
|
+
path: worktreePath,
|
|
58
|
+
branch,
|
|
59
|
+
originalCwd,
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
process.chdir(worktreePath);
|
|
63
|
+
|
|
64
|
+
return `Entered worktree:\n Branch: ${branch}\n Path: ${worktreePath}\n Original: ${originalCwd}`;
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return `Error creating worktree: ${err.message}`;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExitWorktree Tool — exit and clean up a git worktree.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { execSync } from 'child_process';
|
|
6
|
+
|
|
7
|
+
export const ExitWorktreeTool = {
|
|
8
|
+
name: 'ExitWorktree',
|
|
9
|
+
description: 'Exit the current git worktree and return to the original directory.',
|
|
10
|
+
inputSchema: {
|
|
11
|
+
type: 'object',
|
|
12
|
+
properties: {
|
|
13
|
+
cleanup: {
|
|
14
|
+
type: 'boolean',
|
|
15
|
+
description: 'Remove the worktree branch after exiting (default: true)',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
required: [],
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
validateInput() { return []; },
|
|
22
|
+
|
|
23
|
+
async call(input) {
|
|
24
|
+
const { EnterWorktreeTool } = await import('./enter-worktree.mjs');
|
|
25
|
+
const wt = EnterWorktreeTool._activeWorktree;
|
|
26
|
+
|
|
27
|
+
if (!wt) {
|
|
28
|
+
return 'Not currently in a worktree.';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const cleanup = input.cleanup !== false;
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
process.chdir(wt.originalCwd);
|
|
35
|
+
|
|
36
|
+
if (cleanup) {
|
|
37
|
+
try {
|
|
38
|
+
execSync(`git worktree remove "${wt.path}" --force`, {
|
|
39
|
+
encoding: 'utf-8',
|
|
40
|
+
stdio: 'pipe',
|
|
41
|
+
});
|
|
42
|
+
execSync(`git branch -D "${wt.branch}"`, {
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
stdio: 'pipe',
|
|
45
|
+
});
|
|
46
|
+
} catch {
|
|
47
|
+
// Best effort cleanup
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
EnterWorktreeTool._activeWorktree = null;
|
|
52
|
+
return `Exited worktree. Returned to ${wt.originalCwd}${cleanup ? ' (cleaned up)' : ''}`;
|
|
53
|
+
} catch (err) {
|
|
54
|
+
return `Error exiting worktree: ${err.message}`;
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Glob Tool — matches Claude Code's exact behavior.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Proper glob matching (not shell find)
|
|
6
|
+
* - Sort by modification time
|
|
7
|
+
* - path parameter for directory scoping
|
|
8
|
+
*/
|
|
9
|
+
import fs from 'fs';
|
|
10
|
+
import path from 'path';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Minimal glob implementation without external dependencies.
|
|
14
|
+
* Supports *, **, and ? wildcards.
|
|
15
|
+
*/
|
|
16
|
+
function globMatch(pattern, str) {
|
|
17
|
+
const regex = globToRegex(pattern);
|
|
18
|
+
return regex.test(str);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function globToRegex(pattern) {
|
|
22
|
+
let regex = '';
|
|
23
|
+
let i = 0;
|
|
24
|
+
while (i < pattern.length) {
|
|
25
|
+
const ch = pattern[i];
|
|
26
|
+
if (ch === '*') {
|
|
27
|
+
if (pattern[i + 1] === '*') {
|
|
28
|
+
// ** matches everything including /
|
|
29
|
+
regex += '.*';
|
|
30
|
+
i += 2;
|
|
31
|
+
if (pattern[i] === '/') i++; // skip trailing /
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
regex += '[^/]*';
|
|
35
|
+
} else if (ch === '?') {
|
|
36
|
+
regex += '[^/]';
|
|
37
|
+
} else if (ch === '.') {
|
|
38
|
+
regex += '\\.';
|
|
39
|
+
} else if (ch === '{') {
|
|
40
|
+
regex += '(';
|
|
41
|
+
} else if (ch === '}') {
|
|
42
|
+
regex += ')';
|
|
43
|
+
} else if (ch === ',') {
|
|
44
|
+
regex += '|';
|
|
45
|
+
} else {
|
|
46
|
+
regex += ch;
|
|
47
|
+
}
|
|
48
|
+
i++;
|
|
49
|
+
}
|
|
50
|
+
return new RegExp('^' + regex + '$');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function walkDir(dir, maxDepth = 20, depth = 0) {
|
|
54
|
+
const results = [];
|
|
55
|
+
if (depth > maxDepth) return results;
|
|
56
|
+
try {
|
|
57
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
58
|
+
for (const entry of entries) {
|
|
59
|
+
// Skip hidden dirs and node_modules
|
|
60
|
+
if (entry.name.startsWith('.') && depth > 0) continue;
|
|
61
|
+
if (entry.name === 'node_modules') continue;
|
|
62
|
+
|
|
63
|
+
const full = path.join(dir, entry.name);
|
|
64
|
+
if (entry.isDirectory()) {
|
|
65
|
+
results.push(...walkDir(full, maxDepth, depth + 1));
|
|
66
|
+
} else {
|
|
67
|
+
results.push(full);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
} catch {
|
|
71
|
+
// Permission denied or other error
|
|
72
|
+
}
|
|
73
|
+
return results;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export const GlobTool = {
|
|
77
|
+
name: 'Glob',
|
|
78
|
+
description: 'Find files matching a glob pattern, sorted by modification time.',
|
|
79
|
+
inputSchema: {
|
|
80
|
+
type: 'object',
|
|
81
|
+
properties: {
|
|
82
|
+
pattern: { type: 'string', description: 'Glob pattern (e.g. "**/*.js")' },
|
|
83
|
+
path: { type: 'string', description: 'Directory to search in' },
|
|
84
|
+
},
|
|
85
|
+
required: ['pattern'],
|
|
86
|
+
},
|
|
87
|
+
validateInput(input) { return input.pattern ? [] : ['pattern required']; },
|
|
88
|
+
async call(input) {
|
|
89
|
+
try {
|
|
90
|
+
const baseDir = path.resolve(input.path || '.');
|
|
91
|
+
if (!fs.existsSync(baseDir)) {
|
|
92
|
+
return `Error: Directory not found: ${baseDir}`;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const allFiles = walkDir(baseDir);
|
|
96
|
+
|
|
97
|
+
// Match against pattern
|
|
98
|
+
const matches = allFiles.filter(f => {
|
|
99
|
+
const rel = path.relative(baseDir, f);
|
|
100
|
+
return globMatch(input.pattern, rel) || globMatch(input.pattern, path.basename(f));
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
if (matches.length === 0) return 'No matches found.';
|
|
104
|
+
|
|
105
|
+
// Sort by modification time (newest first)
|
|
106
|
+
matches.sort((a, b) => {
|
|
107
|
+
try {
|
|
108
|
+
return fs.statSync(b).mtimeMs - fs.statSync(a).mtimeMs;
|
|
109
|
+
} catch { return 0; }
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
return matches.join('\n');
|
|
113
|
+
} catch (e) {
|
|
114
|
+
return `Error: ${e.message}`;
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Grep Tool — matches Claude Code's exact behavior.
|
|
3
|
+
*
|
|
4
|
+
* Features:
|
|
5
|
+
* - Case insensitive (-i)
|
|
6
|
+
* - Line numbers (-n, default true for content mode)
|
|
7
|
+
* - Context lines (-A, -B, -C)
|
|
8
|
+
* - output_mode: content, files_with_matches, count
|
|
9
|
+
* - glob filter and type filter
|
|
10
|
+
* - head_limit (default 250)
|
|
11
|
+
* - multiline mode
|
|
12
|
+
*/
|
|
13
|
+
import { spawnSync } from 'child_process';
|
|
14
|
+
import path from 'path';
|
|
15
|
+
|
|
16
|
+
export const GrepTool = {
|
|
17
|
+
name: 'Grep',
|
|
18
|
+
description: 'Search file contents with regex (powered by ripgrep or grep).',
|
|
19
|
+
inputSchema: {
|
|
20
|
+
type: 'object',
|
|
21
|
+
properties: {
|
|
22
|
+
pattern: { type: 'string', description: 'Regex pattern to search for' },
|
|
23
|
+
path: { type: 'string', description: 'File or directory to search in' },
|
|
24
|
+
'-i': { type: 'boolean', description: 'Case insensitive' },
|
|
25
|
+
'-n': { type: 'boolean', description: 'Show line numbers (default true)' },
|
|
26
|
+
'-A': { type: 'number', description: 'Lines after match' },
|
|
27
|
+
'-B': { type: 'number', description: 'Lines before match' },
|
|
28
|
+
'-C': { type: 'number', description: 'Context lines (before and after)' },
|
|
29
|
+
context: { type: 'number', description: 'Alias for -C' },
|
|
30
|
+
output_mode: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
enum: ['content', 'files_with_matches', 'count'],
|
|
33
|
+
description: 'Output mode (default: files_with_matches)',
|
|
34
|
+
},
|
|
35
|
+
glob: { type: 'string', description: 'Glob pattern to filter files' },
|
|
36
|
+
type: { type: 'string', description: 'File type filter (e.g. js, py)' },
|
|
37
|
+
head_limit: { type: 'number', description: 'Max output lines (default 250)' },
|
|
38
|
+
multiline: { type: 'boolean', description: 'Enable multiline matching' },
|
|
39
|
+
},
|
|
40
|
+
required: ['pattern'],
|
|
41
|
+
},
|
|
42
|
+
validateInput(input) { return input.pattern ? [] : ['pattern required']; },
|
|
43
|
+
async call(input) {
|
|
44
|
+
try {
|
|
45
|
+
const dir = path.resolve(input.path || '.');
|
|
46
|
+
const mode = input.output_mode || 'files_with_matches';
|
|
47
|
+
const limit = input.head_limit ?? 250;
|
|
48
|
+
|
|
49
|
+
// Pass regex and paths as process arguments. Joining these into a
|
|
50
|
+
// shell command would turn regex operators such as | into pipelines.
|
|
51
|
+
const args = [];
|
|
52
|
+
const useRg = hasRipgrep();
|
|
53
|
+
const command = useRg ? 'rg' : 'grep';
|
|
54
|
+
|
|
55
|
+
if (useRg) {
|
|
56
|
+
if (input['-i']) args.push('-i');
|
|
57
|
+
if (input.multiline) args.push('-U', '--multiline-dotall');
|
|
58
|
+
|
|
59
|
+
if (mode === 'files_with_matches') {
|
|
60
|
+
args.push('-l');
|
|
61
|
+
} else if (mode === 'count') {
|
|
62
|
+
args.push('-c');
|
|
63
|
+
} else {
|
|
64
|
+
// content mode
|
|
65
|
+
const showLineNumbers = input['-n'] !== false;
|
|
66
|
+
if (showLineNumbers) args.push('-n');
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const ctx = input['-C'] || input.context;
|
|
70
|
+
if (ctx && mode === 'content') args.push('-C', String(ctx));
|
|
71
|
+
if (input['-A'] && mode === 'content') args.push('-A', String(input['-A']));
|
|
72
|
+
if (input['-B'] && mode === 'content') args.push('-B', String(input['-B']));
|
|
73
|
+
|
|
74
|
+
if (input.glob) args.push('--glob', input.glob);
|
|
75
|
+
if (input.type) args.push('--type', input.type);
|
|
76
|
+
|
|
77
|
+
args.push('--', input.pattern, dir);
|
|
78
|
+
} else {
|
|
79
|
+
args.push('-r');
|
|
80
|
+
if (input['-i']) args.push('-i');
|
|
81
|
+
|
|
82
|
+
if (mode === 'files_with_matches') {
|
|
83
|
+
args.push('-l');
|
|
84
|
+
} else if (mode === 'count') {
|
|
85
|
+
args.push('-c');
|
|
86
|
+
} else {
|
|
87
|
+
if (input['-n'] !== false) args.push('-n');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const ctx = input['-C'] || input.context;
|
|
91
|
+
if (ctx && mode === 'content') args.push('-C', String(ctx));
|
|
92
|
+
if (input['-A'] && mode === 'content') args.push('-A', String(input['-A']));
|
|
93
|
+
if (input['-B'] && mode === 'content') args.push('-B', String(input['-B']));
|
|
94
|
+
|
|
95
|
+
if (input.glob) args.push('--include', input.glob);
|
|
96
|
+
|
|
97
|
+
args.push('--', input.pattern, dir);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const result = spawnSync(command, args, {
|
|
101
|
+
encoding: 'utf-8',
|
|
102
|
+
maxBuffer: 10 * 1024 * 1024,
|
|
103
|
+
timeout: 30000,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
if (result.error || (result.status !== 0 && result.status !== 1)) {
|
|
107
|
+
return 'No matches found.';
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
const output = (result.stdout || '').trim();
|
|
111
|
+
if (!output) return 'No matches found.';
|
|
112
|
+
if (limit <= 0) return output;
|
|
113
|
+
return output.split('\n').slice(0, limit).join('\n');
|
|
114
|
+
} catch {
|
|
115
|
+
return 'No matches found.';
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
let _hasRg = null;
|
|
121
|
+
function hasRipgrep() {
|
|
122
|
+
if (_hasRg !== null) return _hasRg;
|
|
123
|
+
const result = spawnSync('rg', ['--version'], {
|
|
124
|
+
encoding: 'utf-8',
|
|
125
|
+
timeout: 5000,
|
|
126
|
+
});
|
|
127
|
+
_hasRg = !result.error && result.status === 0;
|
|
128
|
+
return _hasRg;
|
|
129
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Lint Tool — run linters and type checkers.
|
|
3
|
+
*
|
|
4
|
+
* Always safe: read-only, no side effects (unless --fix).
|
|
5
|
+
* Agent should use this instead of shell("ruff check .").
|
|
6
|
+
*
|
|
7
|
+
* Execution: direct on host (no sandbox needed for read-only).
|
|
8
|
+
* With --fix: routes through sandbox if available.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { BashTool } from './bash.mjs';
|
|
12
|
+
|
|
13
|
+
const LINTERS = {
|
|
14
|
+
ruff: { cmd: (p, fix) => `ruff check ${fix ? '--fix ' : ''}${p}`, lang: 'python' },
|
|
15
|
+
mypy: { cmd: (p) => `mypy ${p} --no-error-summary`, lang: 'python' },
|
|
16
|
+
pyright: { cmd: (p) => `pyright ${p}`, lang: 'python' },
|
|
17
|
+
pylint: { cmd: (p) => `pylint ${p} --output-format=text`, lang: 'python' },
|
|
18
|
+
eslint: { cmd: (p, fix) => `eslint ${fix ? '--fix ' : ''}${p}`, lang: 'javascript' },
|
|
19
|
+
tsc: { cmd: () => 'tsc --noEmit', lang: 'typescript' },
|
|
20
|
+
flake8: { cmd: (p) => `flake8 ${p}`, lang: 'python' },
|
|
21
|
+
biome: { cmd: (p, fix) => `biome check ${fix ? '--fix ' : ''}${p}`, lang: 'javascript' },
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const LintTool = {
|
|
25
|
+
name: 'Lint',
|
|
26
|
+
description: 'Run a linter or type checker. Fast, read-only (unless --fix). Use this instead of Bash for code checking.',
|
|
27
|
+
inputSchema: {
|
|
28
|
+
type: 'object',
|
|
29
|
+
properties: {
|
|
30
|
+
tool: {
|
|
31
|
+
type: 'string',
|
|
32
|
+
enum: Object.keys(LINTERS),
|
|
33
|
+
description: 'Which linter to run',
|
|
34
|
+
},
|
|
35
|
+
path: {
|
|
36
|
+
type: 'string',
|
|
37
|
+
description: 'File or directory to lint (default: ".")',
|
|
38
|
+
default: '.',
|
|
39
|
+
},
|
|
40
|
+
fix: {
|
|
41
|
+
type: 'boolean',
|
|
42
|
+
description: 'Apply auto-fixes (default: false)',
|
|
43
|
+
default: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
required: ['tool'],
|
|
47
|
+
},
|
|
48
|
+
validateInput(input) {
|
|
49
|
+
const errors = [];
|
|
50
|
+
if (!input.tool) errors.push('tool is required');
|
|
51
|
+
if (input.tool && !LINTERS[input.tool]) errors.push(`Unknown linter: ${input.tool}. Supported: ${Object.keys(LINTERS).join(', ')}`);
|
|
52
|
+
return errors;
|
|
53
|
+
},
|
|
54
|
+
async call(input) {
|
|
55
|
+
const linter = LINTERS[input.tool];
|
|
56
|
+
if (!linter) return `Unknown linter: ${input.tool}`;
|
|
57
|
+
|
|
58
|
+
const targetPath = input.path || '.';
|
|
59
|
+
const command = linter.cmd(targetPath, input.fix);
|
|
60
|
+
|
|
61
|
+
const result = await BashTool.call({
|
|
62
|
+
command,
|
|
63
|
+
timeout: 30000, // linters should be fast
|
|
64
|
+
description: `Lint: ${input.tool} ${targetPath}`,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
return result;
|
|
68
|
+
},
|
|
69
|
+
// Metadata for execution policy
|
|
70
|
+
_executionPolicy: 'direct', // no sandbox for read-only; 'contained' if fix=true
|
|
71
|
+
};
|
package/src/tools/ls.mjs
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LS Tool — directory listing with metadata.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
|
|
8
|
+
export const LsTool = {
|
|
9
|
+
name: 'LS',
|
|
10
|
+
description: 'List directory contents with file sizes and types.',
|
|
11
|
+
inputSchema: {
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
path: { type: 'string', description: 'Directory path to list (default: cwd)' },
|
|
15
|
+
all: { type: 'boolean', description: 'Include hidden files (default: false)' },
|
|
16
|
+
},
|
|
17
|
+
required: [],
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
validateInput() { return []; },
|
|
21
|
+
|
|
22
|
+
async call(input) {
|
|
23
|
+
const dirPath = path.resolve(input.path || '.');
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const entries = fs.readdirSync(dirPath, { withFileTypes: true });
|
|
27
|
+
const results = [];
|
|
28
|
+
|
|
29
|
+
for (const entry of entries) {
|
|
30
|
+
if (!input.all && entry.name.startsWith('.')) continue;
|
|
31
|
+
|
|
32
|
+
const fullPath = path.join(dirPath, entry.name);
|
|
33
|
+
let size = '';
|
|
34
|
+
try {
|
|
35
|
+
const stat = fs.statSync(fullPath);
|
|
36
|
+
size = entry.isDirectory() ? '' : formatSize(stat.size);
|
|
37
|
+
} catch {
|
|
38
|
+
size = '?';
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const type = entry.isDirectory() ? 'd' : entry.isSymbolicLink() ? 'l' : '-';
|
|
42
|
+
results.push(`${type} ${size.padStart(8)} ${entry.name}${entry.isDirectory() ? '/' : ''}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (results.length === 0) return 'Empty directory';
|
|
46
|
+
return `${dirPath}:\n${results.join('\n')}`;
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return `Error: ${err.message}`;
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
function formatSize(bytes) {
|
|
54
|
+
if (bytes < 1024) return `${bytes}B`;
|
|
55
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}K`;
|
|
56
|
+
if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}M`;
|
|
57
|
+
return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}G`;
|
|
58
|
+
}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LSP Tool — Language Server Protocol integration stub.
|
|
3
|
+
*
|
|
4
|
+
* Provides a tool interface for language server features:
|
|
5
|
+
* - diagnostics (errors/warnings)
|
|
6
|
+
* - completions
|
|
7
|
+
* - hover information
|
|
8
|
+
* - go-to-definition
|
|
9
|
+
* - references
|
|
10
|
+
*
|
|
11
|
+
* This is a stub implementation. Full LSP would spawn an actual language
|
|
12
|
+
* server process and communicate via JSON-RPC.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { execSync } from 'child_process';
|
|
16
|
+
import path from 'path';
|
|
17
|
+
|
|
18
|
+
export const LspTool = {
|
|
19
|
+
name: 'LSP',
|
|
20
|
+
description: 'Query language server for diagnostics, completions, hover, and definitions.',
|
|
21
|
+
inputSchema: {
|
|
22
|
+
type: 'object',
|
|
23
|
+
properties: {
|
|
24
|
+
action: {
|
|
25
|
+
type: 'string',
|
|
26
|
+
enum: ['diagnostics', 'completions', 'hover', 'definition', 'references'],
|
|
27
|
+
description: 'LSP action to perform',
|
|
28
|
+
},
|
|
29
|
+
file: {
|
|
30
|
+
type: 'string',
|
|
31
|
+
description: 'File path to query',
|
|
32
|
+
},
|
|
33
|
+
line: {
|
|
34
|
+
type: 'number',
|
|
35
|
+
description: 'Line number (0-based)',
|
|
36
|
+
},
|
|
37
|
+
character: {
|
|
38
|
+
type: 'number',
|
|
39
|
+
description: 'Character position (0-based)',
|
|
40
|
+
},
|
|
41
|
+
language: {
|
|
42
|
+
type: 'string',
|
|
43
|
+
description: 'Language ID (e.g., "typescript", "python")',
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
required: ['action', 'file'],
|
|
47
|
+
},
|
|
48
|
+
|
|
49
|
+
validateInput(input) {
|
|
50
|
+
const errors = [];
|
|
51
|
+
if (!input.action) errors.push('action is required');
|
|
52
|
+
if (!input.file) errors.push('file is required');
|
|
53
|
+
return errors;
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
async call(input) {
|
|
57
|
+
const filePath = path.resolve(input.file);
|
|
58
|
+
const action = input.action;
|
|
59
|
+
|
|
60
|
+
switch (action) {
|
|
61
|
+
case 'diagnostics':
|
|
62
|
+
return getDiagnostics(filePath, input.language);
|
|
63
|
+
case 'completions':
|
|
64
|
+
return `[LSP stub] Completions at ${filePath}:${input.line}:${input.character} not yet implemented.`;
|
|
65
|
+
case 'hover':
|
|
66
|
+
return `[LSP stub] Hover at ${filePath}:${input.line}:${input.character} not yet implemented.`;
|
|
67
|
+
case 'definition':
|
|
68
|
+
return `[LSP stub] Go-to-definition at ${filePath}:${input.line}:${input.character} not yet implemented.`;
|
|
69
|
+
case 'references':
|
|
70
|
+
return `[LSP stub] Find references at ${filePath}:${input.line}:${input.character} not yet implemented.`;
|
|
71
|
+
default:
|
|
72
|
+
return `Unknown LSP action: ${action}`;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
function getDiagnostics(filePath, language) {
|
|
78
|
+
const ext = path.extname(filePath);
|
|
79
|
+
const lang = language || extToLanguage(ext);
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
switch (lang) {
|
|
83
|
+
case 'typescript':
|
|
84
|
+
case 'javascript': {
|
|
85
|
+
const result = execSync(
|
|
86
|
+
`npx tsc --noEmit --pretty false "${filePath}" 2>&1 || true`,
|
|
87
|
+
{ encoding: 'utf-8', timeout: 15000 }
|
|
88
|
+
);
|
|
89
|
+
return result.trim() || 'No diagnostics.';
|
|
90
|
+
}
|
|
91
|
+
case 'python': {
|
|
92
|
+
const result = execSync(
|
|
93
|
+
`python3 -m py_compile "${filePath}" 2>&1 || true`,
|
|
94
|
+
{ encoding: 'utf-8', timeout: 10000 }
|
|
95
|
+
);
|
|
96
|
+
return result.trim() || 'No diagnostics.';
|
|
97
|
+
}
|
|
98
|
+
default:
|
|
99
|
+
return `[LSP stub] No diagnostic provider for language: ${lang}`;
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
return `Diagnostics error: ${err.message}`;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function extToLanguage(ext) {
|
|
107
|
+
const map = {
|
|
108
|
+
'.ts': 'typescript', '.tsx': 'typescript',
|
|
109
|
+
'.js': 'javascript', '.jsx': 'javascript', '.mjs': 'javascript',
|
|
110
|
+
'.py': 'python',
|
|
111
|
+
'.rs': 'rust', '.go': 'go', '.java': 'java',
|
|
112
|
+
'.rb': 'ruby', '.php': 'php', '.cs': 'csharp',
|
|
113
|
+
};
|
|
114
|
+
return map[ext] || 'unknown';
|
|
115
|
+
}
|