0xkobold 0.0.1
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/.agents/skills/nextjs-best-practices/SKILL.md +208 -0
- package/.agents/skills/sql-optimization-patterns/SKILL.md +509 -0
- package/HEARTBEAT.md +45 -0
- package/README.md +197 -0
- package/USAGE.md +191 -0
- package/dist/package.json +77 -0
- package/dist/src/agent/pi-adapter.js +307 -0
- package/dist/src/agent/pi-adapter.js.map +1 -0
- package/dist/src/agent/tool-adapter.js +86 -0
- package/dist/src/agent/tool-adapter.js.map +1 -0
- package/dist/src/approval/queue.js +114 -0
- package/dist/src/approval/queue.js.map +1 -0
- package/dist/src/ascii-kobold.js +76 -0
- package/dist/src/ascii-kobold.js.map +1 -0
- package/dist/src/cli/client.js +217 -0
- package/dist/src/cli/client.js.map +1 -0
- package/dist/src/cli/commands/agent.js +272 -0
- package/dist/src/cli/commands/agent.js.map +1 -0
- package/dist/src/cli/commands/chat.js +234 -0
- package/dist/src/cli/commands/chat.js.map +1 -0
- package/dist/src/cli/commands/config.js +202 -0
- package/dist/src/cli/commands/config.js.map +1 -0
- package/dist/src/cli/commands/daemon.js +203 -0
- package/dist/src/cli/commands/daemon.js.map +1 -0
- package/dist/src/cli/commands/gateway.js +184 -0
- package/dist/src/cli/commands/gateway.js.map +1 -0
- package/dist/src/cli/commands/init.js +175 -0
- package/dist/src/cli/commands/init.js.map +1 -0
- package/dist/src/cli/commands/kobold.js +21 -0
- package/dist/src/cli/commands/kobold.js.map +1 -0
- package/dist/src/cli/commands/logs.js +27 -0
- package/dist/src/cli/commands/logs.js.map +1 -0
- package/dist/src/cli/commands/mode.js +121 -0
- package/dist/src/cli/commands/mode.js.map +1 -0
- package/dist/src/cli/commands/persona.js +261 -0
- package/dist/src/cli/commands/persona.js.map +1 -0
- package/dist/src/cli/commands/start.js +66 -0
- package/dist/src/cli/commands/start.js.map +1 -0
- package/dist/src/cli/commands/status.js +117 -0
- package/dist/src/cli/commands/status.js.map +1 -0
- package/dist/src/cli/commands/stop.js +27 -0
- package/dist/src/cli/commands/stop.js.map +1 -0
- package/dist/src/cli/commands/system.js +128 -0
- package/dist/src/cli/commands/system.js.map +1 -0
- package/dist/src/cli/commands/tui.js +103 -0
- package/dist/src/cli/commands/tui.js.map +1 -0
- package/dist/src/cli/commands/update.js +133 -0
- package/dist/src/cli/commands/update.js.map +1 -0
- package/dist/src/cli/extensions/discord.js +113 -0
- package/dist/src/cli/extensions/discord.js.map +1 -0
- package/dist/src/cli/extensions/env.js +91 -0
- package/dist/src/cli/extensions/env.js.map +1 -0
- package/dist/src/cli/extensions/heartbeat.js +78 -0
- package/dist/src/cli/extensions/heartbeat.js.map +1 -0
- package/dist/src/cli/index.js +24 -0
- package/dist/src/cli/index.js.map +1 -0
- package/dist/src/cli/program.js +70 -0
- package/dist/src/cli/program.js.map +1 -0
- package/dist/src/cli/repl.js +184 -0
- package/dist/src/cli/repl.js.map +1 -0
- package/dist/src/cli/shared/discord-service.js +102 -0
- package/dist/src/cli/shared/discord-service.js.map +1 -0
- package/dist/src/config/index.js +10 -0
- package/dist/src/config/index.js.map +1 -0
- package/dist/src/config/loader.js +401 -0
- package/dist/src/config/loader.js.map +1 -0
- package/dist/src/config/paths.js +84 -0
- package/dist/src/config/paths.js.map +1 -0
- package/dist/src/config/types.js +8 -0
- package/dist/src/config/types.js.map +1 -0
- package/dist/src/context-detector.js +60 -0
- package/dist/src/context-detector.js.map +1 -0
- package/dist/src/discord/index.js +376 -0
- package/dist/src/discord/index.js.map +1 -0
- package/dist/src/event-bus/index.js +97 -0
- package/dist/src/event-bus/index.js.map +1 -0
- package/dist/src/extensions/command-args.js +68 -0
- package/dist/src/extensions/command-args.js.map +1 -0
- package/dist/src/extensions/core/agent-registry-extension.js +541 -0
- package/dist/src/extensions/core/agent-registry-extension.js.map +1 -0
- package/dist/src/extensions/core/agent-worker.js +148 -0
- package/dist/src/extensions/core/agent-worker.js.map +1 -0
- package/dist/src/extensions/core/compaction-safeguard.js +154 -0
- package/dist/src/extensions/core/compaction-safeguard.js.map +1 -0
- package/dist/src/extensions/core/confirm-destructive.js +43 -0
- package/dist/src/extensions/core/confirm-destructive.js.map +1 -0
- package/dist/src/extensions/core/context-aware-extension.js +124 -0
- package/dist/src/extensions/core/context-aware-extension.js.map +1 -0
- package/dist/src/extensions/core/context-pruning/extension.js +124 -0
- package/dist/src/extensions/core/context-pruning/extension.js.map +1 -0
- package/dist/src/extensions/core/context-pruning/pruner.js +312 -0
- package/dist/src/extensions/core/context-pruning/pruner.js.map +1 -0
- package/dist/src/extensions/core/context-pruning/runtime.js +48 -0
- package/dist/src/extensions/core/context-pruning/runtime.js.map +1 -0
- package/dist/src/extensions/core/context-pruning/settings.js +105 -0
- package/dist/src/extensions/core/context-pruning/settings.js.map +1 -0
- package/dist/src/extensions/core/dirty-repo-guard.js +42 -0
- package/dist/src/extensions/core/dirty-repo-guard.js.map +1 -0
- package/dist/src/extensions/core/discord-channel-extension.js +205 -0
- package/dist/src/extensions/core/discord-channel-extension.js.map +1 -0
- package/dist/src/extensions/core/discord-extension.js +142 -0
- package/dist/src/extensions/core/discord-extension.js.map +1 -0
- package/dist/src/extensions/core/env-loader-extension.js +157 -0
- package/dist/src/extensions/core/env-loader-extension.js.map +1 -0
- package/dist/src/extensions/core/fileops-extension.js +699 -0
- package/dist/src/extensions/core/fileops-extension.js.map +1 -0
- package/dist/src/extensions/core/gateway-extension.js +730 -0
- package/dist/src/extensions/core/gateway-extension.js.map +1 -0
- package/dist/src/extensions/core/git-checkpoint.js +46 -0
- package/dist/src/extensions/core/git-checkpoint.js.map +1 -0
- package/dist/src/extensions/core/handoff-extension.js +206 -0
- package/dist/src/extensions/core/handoff-extension.js.map +1 -0
- package/dist/src/extensions/core/heartbeat-extension.js +373 -0
- package/dist/src/extensions/core/heartbeat-extension.js.map +1 -0
- package/dist/src/extensions/core/mcp-extension.js +413 -0
- package/dist/src/extensions/core/mcp-extension.js.map +1 -0
- package/dist/src/extensions/core/mode-manager-extension.js +562 -0
- package/dist/src/extensions/core/mode-manager-extension.js.map +1 -0
- package/dist/src/extensions/core/multi-channel-extension.js +435 -0
- package/dist/src/extensions/core/multi-channel-extension.js.map +1 -0
- package/dist/src/extensions/core/ollama-provider-extension.js +66 -0
- package/dist/src/extensions/core/ollama-provider-extension.js.map +1 -0
- package/dist/src/extensions/core/onboarding-extension.js +122 -0
- package/dist/src/extensions/core/onboarding-extension.js.map +1 -0
- package/dist/src/extensions/core/persona-loader-extension.js +139 -0
- package/dist/src/extensions/core/persona-loader-extension.js.map +1 -0
- package/dist/src/extensions/core/pi-notify-extension.js +70 -0
- package/dist/src/extensions/core/pi-notify-extension.js.map +1 -0
- package/dist/src/extensions/core/protected-paths.js +24 -0
- package/dist/src/extensions/core/protected-paths.js.map +1 -0
- package/dist/src/extensions/core/questionnaire-extension.js +242 -0
- package/dist/src/extensions/core/questionnaire-extension.js.map +1 -0
- package/dist/src/extensions/core/self-update-extension.js +181 -0
- package/dist/src/extensions/core/self-update-extension.js.map +1 -0
- package/dist/src/extensions/core/session-bridge-extension.js +78 -0
- package/dist/src/extensions/core/session-bridge-extension.js.map +1 -0
- package/dist/src/extensions/core/session-manager-extension.js +319 -0
- package/dist/src/extensions/core/session-manager-extension.js.map +1 -0
- package/dist/src/extensions/core/session-name-extension.js +88 -0
- package/dist/src/extensions/core/session-name-extension.js.map +1 -0
- package/dist/src/extensions/core/session-pruning-extension.js +480 -0
- package/dist/src/extensions/core/session-pruning-extension.js.map +1 -0
- package/dist/src/extensions/core/task-manager-extension.js +661 -0
- package/dist/src/extensions/core/task-manager-extension.js.map +1 -0
- package/dist/src/extensions/core/update-extension.js +438 -0
- package/dist/src/extensions/core/update-extension.js.map +1 -0
- package/dist/src/extensions/core/websearch-extension.js +463 -0
- package/dist/src/extensions/core/websearch-extension.js.map +1 -0
- package/dist/src/extensions/index.js +5 -0
- package/dist/src/extensions/index.js.map +1 -0
- package/dist/src/extensions/loader.js +80 -0
- package/dist/src/extensions/loader.js.map +1 -0
- package/dist/src/gateway/index.js +353 -0
- package/dist/src/gateway/index.js.map +1 -0
- package/dist/src/index.js +150 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/llm/anthropic.js +86 -0
- package/dist/src/llm/anthropic.js.map +1 -0
- package/dist/src/llm/index.js +9 -0
- package/dist/src/llm/index.js.map +1 -0
- package/dist/src/llm/ollama.js +113 -0
- package/dist/src/llm/ollama.js.map +1 -0
- package/dist/src/llm/router.js +145 -0
- package/dist/src/llm/router.js.map +1 -0
- package/dist/src/llm/types.js +7 -0
- package/dist/src/llm/types.js.map +1 -0
- package/dist/src/memory/index.js +5 -0
- package/dist/src/memory/index.js.map +1 -0
- package/dist/src/memory/store.js +91 -0
- package/dist/src/memory/store.js.map +1 -0
- package/dist/src/pi-config.js +80 -0
- package/dist/src/pi-config.js.map +1 -0
- package/dist/src/skills/builtin/file.js +184 -0
- package/dist/src/skills/builtin/file.js.map +1 -0
- package/dist/src/skills/builtin/shell.js +100 -0
- package/dist/src/skills/builtin/shell.js.map +1 -0
- package/dist/src/skills/builtin/subagent.js +62 -0
- package/dist/src/skills/builtin/subagent.js.map +1 -0
- package/dist/src/skills/hello.js +42 -0
- package/dist/src/skills/hello.js.map +1 -0
- package/dist/src/skills/index.js +11 -0
- package/dist/src/skills/index.js.map +1 -0
- package/dist/src/skills/loader.js +382 -0
- package/dist/src/skills/loader.js.map +1 -0
- package/dist/src/skills/types.js +8 -0
- package/dist/src/skills/types.js.map +1 -0
- package/dist/src/utils/working-dir.js +71 -0
- package/dist/src/utils/working-dir.js.map +1 -0
- package/package.json +77 -0
- package/skills/1password/SKILL.md +70 -0
- package/skills/1password/references/cli-examples.md +29 -0
- package/skills/1password/references/get-started.md +17 -0
- package/skills/apple-notes/SKILL.md +77 -0
- package/skills/apple-reminders/SKILL.md +118 -0
- package/skills/bear-notes/SKILL.md +107 -0
- package/skills/blogwatcher/SKILL.md +69 -0
- package/skills/blucli/SKILL.md +47 -0
- package/skills/bluebubbles/SKILL.md +131 -0
- package/skills/camsnap/SKILL.md +45 -0
- package/skills/canvas/SKILL.md +198 -0
- package/skills/clawhub/SKILL.md +77 -0
- package/skills/coding-agent/SKILL.md +284 -0
- package/skills/discord/SKILL.md +197 -0
- package/skills/eightctl/SKILL.md +50 -0
- package/skills/food-order/SKILL.md +48 -0
- package/skills/gemini/SKILL.md +43 -0
- package/skills/gh-issues/SKILL.md +865 -0
- package/skills/gifgrep/SKILL.md +79 -0
- package/skills/github/SKILL.md +163 -0
- package/skills/gog/SKILL.md +116 -0
- package/skills/goplaces/SKILL.md +52 -0
- package/skills/healthcheck/SKILL.md +245 -0
- package/skills/himalaya/SKILL.md +257 -0
- package/skills/himalaya/references/configuration.md +184 -0
- package/skills/himalaya/references/message-composition.md +199 -0
- package/skills/imsg/SKILL.md +122 -0
- package/skills/mcporter/SKILL.md +61 -0
- package/skills/model-usage/SKILL.md +69 -0
- package/skills/model-usage/references/codexbar-cli.md +33 -0
- package/skills/model-usage/scripts/model_usage.py +310 -0
- package/skills/nano-banana-pro/SKILL.md +58 -0
- package/skills/nano-banana-pro/scripts/generate_image.py +184 -0
- package/skills/nano-pdf/SKILL.md +38 -0
- package/skills/notion/SKILL.md +172 -0
- package/skills/obsidian/SKILL.md +81 -0
- package/skills/openai-image-gen/SKILL.md +89 -0
- package/skills/openai-image-gen/scripts/gen.py +240 -0
- package/skills/openai-whisper/SKILL.md +38 -0
- package/skills/openai-whisper-api/SKILL.md +52 -0
- package/skills/openai-whisper-api/scripts/transcribe.sh +85 -0
- package/skills/openhue/SKILL.md +112 -0
- package/skills/oracle/SKILL.md +125 -0
- package/skills/ordercli/SKILL.md +78 -0
- package/skills/peekaboo/SKILL.md +190 -0
- package/skills/sag/SKILL.md +87 -0
- package/skills/session-logs/SKILL.md +115 -0
- package/skills/sherpa-onnx-tts/SKILL.md +103 -0
- package/skills/sherpa-onnx-tts/bin/sherpa-onnx-tts +178 -0
- package/skills/skill-creator/SKILL.md +370 -0
- package/skills/skill-creator/license.txt +202 -0
- package/skills/skill-creator/scripts/init_skill.py +378 -0
- package/skills/skill-creator/scripts/package_skill.py +111 -0
- package/skills/skill-creator/scripts/quick_validate.py +101 -0
- package/skills/slack/SKILL.md +144 -0
- package/skills/songsee/SKILL.md +49 -0
- package/skills/sonoscli/SKILL.md +46 -0
- package/skills/spotify-player/SKILL.md +64 -0
- package/skills/summarize/SKILL.md +87 -0
- package/skills/things-mac/SKILL.md +86 -0
- package/skills/tmux/SKILL.md +153 -0
- package/skills/tmux/scripts/find-sessions.sh +112 -0
- package/skills/tmux/scripts/wait-for-text.sh +83 -0
- package/skills/trello/SKILL.md +95 -0
- package/skills/video-frames/SKILL.md +46 -0
- package/skills/video-frames/scripts/frame.sh +81 -0
- package/skills/voice-call/SKILL.md +45 -0
- package/skills/wacli/SKILL.md +72 -0
- package/skills/weather/SKILL.md +112 -0
|
@@ -0,0 +1,699 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File Operations Extension for 0xKobold
|
|
3
|
+
*
|
|
4
|
+
* Provides file system operations as pi-coding-agent tools
|
|
5
|
+
* Ported and enhanced from core/tools.ts
|
|
6
|
+
*/
|
|
7
|
+
import { $ } from 'bun';
|
|
8
|
+
import { join, dirname } from 'path';
|
|
9
|
+
import { existsSync, statSync, readdirSync, mkdirSync, } from 'fs';
|
|
10
|
+
import { readFile, writeFile as writeFileAsync } from 'fs/promises';
|
|
11
|
+
import { glob } from 'glob';
|
|
12
|
+
import { getWorkingDir, validatePathWithinWorkspace, } from '../../utils/working-dir.js';
|
|
13
|
+
// Security configuration
|
|
14
|
+
const MAX_FILE_SIZE = 1024 * 1024; // 1MB
|
|
15
|
+
const SHELL_TIMEOUT_DEFAULT = 30000; // 30 seconds
|
|
16
|
+
// Dangerous shell commands to block
|
|
17
|
+
const DANGEROUS_COMMANDS = [
|
|
18
|
+
'rm -rf /',
|
|
19
|
+
'dd if=',
|
|
20
|
+
'mkfs.',
|
|
21
|
+
':(){ :|:& };:', // fork bomb
|
|
22
|
+
'curl.*|.*sh', // pipe curl to shell
|
|
23
|
+
'wget.*|.*sh', // pipe wget to shell
|
|
24
|
+
'curl.*|.*bash',
|
|
25
|
+
'wget.*|.*bash',
|
|
26
|
+
'> /dev/',
|
|
27
|
+
'/dev/null;',
|
|
28
|
+
'chmod 000',
|
|
29
|
+
'chmod -R 000',
|
|
30
|
+
];
|
|
31
|
+
/**
|
|
32
|
+
* Validate path - delegates to shared utility with extension-specific error handling
|
|
33
|
+
*/
|
|
34
|
+
function validatePath(inputPath) {
|
|
35
|
+
const result = validatePathWithinWorkspace(inputPath);
|
|
36
|
+
if ('error' in result) {
|
|
37
|
+
return { valid: false, error: result.error, resolvedPath: result.resolvedPath };
|
|
38
|
+
}
|
|
39
|
+
return { valid: true, resolvedPath: result.resolvedPath };
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Check if command is safe to execute
|
|
43
|
+
*/
|
|
44
|
+
function isCommandSafe(command) {
|
|
45
|
+
const lowerCommand = command.toLowerCase();
|
|
46
|
+
for (const pattern of DANGEROUS_COMMANDS) {
|
|
47
|
+
const regex = new RegExp(pattern, 'i');
|
|
48
|
+
if (regex.test(lowerCommand)) {
|
|
49
|
+
return { safe: false, reason: `Command matches dangerous pattern: ${pattern}` };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { safe: true };
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* File Operations Extension
|
|
56
|
+
*/
|
|
57
|
+
export default function fileOpsExtension(pi) {
|
|
58
|
+
// Initialize working directory from CLI flag if provided so that tests and
|
|
59
|
+
// local mode can control the workspace root for all file operations.
|
|
60
|
+
const workingDirFlag = pi.getFlag?.('working-dir');
|
|
61
|
+
if (workingDirFlag) {
|
|
62
|
+
process.env.KOBOLD_WORKING_DIR = String(workingDirFlag);
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Tool: read_file_with_line_numbers
|
|
66
|
+
* Read file with line numbers for context
|
|
67
|
+
*/
|
|
68
|
+
pi.registerTool({
|
|
69
|
+
name: 'read_file_with_line_numbers',
|
|
70
|
+
description: 'Read the contents of a file with line numbers prepended. Useful for viewing code with context. Max file size 1MB.',
|
|
71
|
+
// @ts-ignore TSchema mismatch
|
|
72
|
+
parameters: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
path: {
|
|
76
|
+
type: 'string',
|
|
77
|
+
description: 'The path to the file to read',
|
|
78
|
+
},
|
|
79
|
+
offset: {
|
|
80
|
+
type: 'number',
|
|
81
|
+
description: 'Line number to start reading from (0-indexed, default: 0)',
|
|
82
|
+
},
|
|
83
|
+
limit: {
|
|
84
|
+
type: 'number',
|
|
85
|
+
description: 'Maximum number of lines to read (default: read all)',
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
required: ['path'],
|
|
89
|
+
},
|
|
90
|
+
async execute(args) {
|
|
91
|
+
try {
|
|
92
|
+
const filePath = String(args.path);
|
|
93
|
+
const offset = typeof args.offset === 'number' ? args.offset : 0;
|
|
94
|
+
const limit = typeof args.limit === 'number' ? args.limit : undefined;
|
|
95
|
+
// Validate path
|
|
96
|
+
const pathCheck = validatePath(filePath);
|
|
97
|
+
if (!pathCheck.valid) {
|
|
98
|
+
// If the resolved path does not exist, surface a file_not_found error
|
|
99
|
+
// instead of a workspace escape error (for test and UX friendliness).
|
|
100
|
+
if (!existsSync(pathCheck.resolvedPath)) {
|
|
101
|
+
return {
|
|
102
|
+
content: [{ type: 'text', text: `File not found: ${filePath}` }],
|
|
103
|
+
details: { error: 'file_not_found' },
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
return {
|
|
107
|
+
content: [{ type: 'text', text: `Path validation failed: ${pathCheck.error}` }],
|
|
108
|
+
details: { error: pathCheck.error },
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const resolvedPath = pathCheck.resolvedPath;
|
|
112
|
+
// Check if file exists
|
|
113
|
+
if (!existsSync(resolvedPath)) {
|
|
114
|
+
return {
|
|
115
|
+
content: [{ type: 'text', text: `File not found: ${filePath}` }],
|
|
116
|
+
details: { error: 'file_not_found' },
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
const stats = statSync(resolvedPath);
|
|
120
|
+
// Check if it's a directory
|
|
121
|
+
if (stats.isDirectory()) {
|
|
122
|
+
return {
|
|
123
|
+
content: [{ type: 'text', text: `Path is a directory: ${filePath}` }],
|
|
124
|
+
details: { error: 'is_directory' },
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
// Check file size
|
|
128
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
129
|
+
return {
|
|
130
|
+
content: [
|
|
131
|
+
{
|
|
132
|
+
type: 'text',
|
|
133
|
+
text: `File too large (${(stats.size / 1024 / 1024).toFixed(2)}MB). Max size is 1MB. Use offset and limit parameters.`,
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
details: { error: 'file_too_large', size: stats.size },
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
// Read file content
|
|
140
|
+
const content = await readFile(resolvedPath, 'utf-8');
|
|
141
|
+
const lines = content.split('\n');
|
|
142
|
+
const endLine = limit ? Math.min(offset + limit, lines.length) : lines.length;
|
|
143
|
+
const selectedLines = lines.slice(offset, endLine);
|
|
144
|
+
// Prepend line numbers (1-indexed for human readability)
|
|
145
|
+
const numberedLines = selectedLines.map((line, idx) => {
|
|
146
|
+
const lineNumber = offset + idx + 1;
|
|
147
|
+
const padding = String(endLine).length;
|
|
148
|
+
return `${String(lineNumber).padStart(padding, ' ')} | ${line}`;
|
|
149
|
+
});
|
|
150
|
+
const result = numberedLines.join('\n');
|
|
151
|
+
return {
|
|
152
|
+
content: [
|
|
153
|
+
{ type: 'text', text: `File: ${filePath} (${lines.length} lines)` },
|
|
154
|
+
{ type: 'text', text: result },
|
|
155
|
+
],
|
|
156
|
+
details: {
|
|
157
|
+
path: filePath,
|
|
158
|
+
size: stats.size,
|
|
159
|
+
totalLines: lines.length,
|
|
160
|
+
displayedLines: selectedLines.length,
|
|
161
|
+
startLine: offset + 1,
|
|
162
|
+
endLine,
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
return {
|
|
168
|
+
content: [{ type: 'text', text: `Error reading file: ${error}` }],
|
|
169
|
+
details: { error: String(error) },
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
},
|
|
173
|
+
});
|
|
174
|
+
/**
|
|
175
|
+
* Tool: write_file
|
|
176
|
+
* Write content to a file
|
|
177
|
+
*/
|
|
178
|
+
pi.registerTool({
|
|
179
|
+
name: 'write_file',
|
|
180
|
+
description: 'Write content to a file at the specified path. Creates directories if needed. Can append to existing files.',
|
|
181
|
+
// @ts-ignore TSchema mismatch
|
|
182
|
+
parameters: {
|
|
183
|
+
type: 'object',
|
|
184
|
+
properties: {
|
|
185
|
+
path: {
|
|
186
|
+
type: 'string',
|
|
187
|
+
description: 'The path to the file to write',
|
|
188
|
+
},
|
|
189
|
+
content: {
|
|
190
|
+
type: 'string',
|
|
191
|
+
description: 'The content to write to the file',
|
|
192
|
+
},
|
|
193
|
+
append: {
|
|
194
|
+
type: 'boolean',
|
|
195
|
+
description: 'If true, append to existing file instead of overwriting (default: false)',
|
|
196
|
+
},
|
|
197
|
+
},
|
|
198
|
+
required: ['path', 'content'],
|
|
199
|
+
},
|
|
200
|
+
async execute(args) {
|
|
201
|
+
try {
|
|
202
|
+
const filePath = String(args.path);
|
|
203
|
+
const content = String(args.content);
|
|
204
|
+
const append = args.append === true;
|
|
205
|
+
// Validate path
|
|
206
|
+
const pathCheck = validatePath(filePath);
|
|
207
|
+
if (!pathCheck.valid) {
|
|
208
|
+
return {
|
|
209
|
+
content: [{ type: 'text', text: `Path validation failed: ${pathCheck.error}` }],
|
|
210
|
+
details: { error: pathCheck.error },
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
const resolvedPath = pathCheck.resolvedPath;
|
|
214
|
+
const dir = dirname(resolvedPath);
|
|
215
|
+
// Create directory if needed
|
|
216
|
+
if (!existsSync(dir)) {
|
|
217
|
+
mkdirSync(dir, { recursive: true });
|
|
218
|
+
}
|
|
219
|
+
// Write or append file
|
|
220
|
+
if (append && existsSync(resolvedPath)) {
|
|
221
|
+
const existing = await readFile(resolvedPath, 'utf-8');
|
|
222
|
+
await writeFileAsync(resolvedPath, existing + content, 'utf-8');
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
await writeFileAsync(resolvedPath, content, 'utf-8');
|
|
226
|
+
}
|
|
227
|
+
const bytes = Buffer.byteLength(content, 'utf-8');
|
|
228
|
+
return {
|
|
229
|
+
content: [
|
|
230
|
+
{
|
|
231
|
+
type: 'text',
|
|
232
|
+
text: `Successfully ${append ? 'appended to' : 'wrote'} file: ${filePath}`,
|
|
233
|
+
},
|
|
234
|
+
],
|
|
235
|
+
details: {
|
|
236
|
+
path: filePath,
|
|
237
|
+
bytes,
|
|
238
|
+
append,
|
|
239
|
+
},
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
content: [{ type: 'text', text: `Error writing file: ${error}` }],
|
|
245
|
+
details: { error: String(error) },
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
});
|
|
250
|
+
/**
|
|
251
|
+
* Tool: list_directory
|
|
252
|
+
* List directory contents
|
|
253
|
+
*/
|
|
254
|
+
pi.registerTool({
|
|
255
|
+
name: 'list_directory',
|
|
256
|
+
description: 'List the contents of a directory. Shows file and folder icons. Can optionally recurse into subdirectories.',
|
|
257
|
+
// @ts-ignore TSchema mismatch
|
|
258
|
+
parameters: {
|
|
259
|
+
type: 'object',
|
|
260
|
+
properties: {
|
|
261
|
+
path: {
|
|
262
|
+
type: 'string',
|
|
263
|
+
description: 'The path to the directory to list',
|
|
264
|
+
},
|
|
265
|
+
recursive: {
|
|
266
|
+
type: 'boolean',
|
|
267
|
+
description: 'If true, recursively list all subdirectories (default: false)',
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
required: ['path'],
|
|
271
|
+
},
|
|
272
|
+
async execute(args) {
|
|
273
|
+
try {
|
|
274
|
+
const dirPath = String(args.path);
|
|
275
|
+
const recursive = args.recursive === true;
|
|
276
|
+
// Validate path
|
|
277
|
+
const pathCheck = validatePath(dirPath);
|
|
278
|
+
if (!pathCheck.valid) {
|
|
279
|
+
// Treat non-existent paths as directory_not_found for friendlier errors
|
|
280
|
+
if (!existsSync(pathCheck.resolvedPath)) {
|
|
281
|
+
return {
|
|
282
|
+
content: [{ type: 'text', text: `Directory not found: ${dirPath}` }],
|
|
283
|
+
details: { error: 'directory_not_found' },
|
|
284
|
+
};
|
|
285
|
+
}
|
|
286
|
+
return {
|
|
287
|
+
content: [{ type: 'text', text: `Path validation failed: ${pathCheck.error}` }],
|
|
288
|
+
details: { error: pathCheck.error },
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
const resolvedPath = pathCheck.resolvedPath;
|
|
292
|
+
// Check if directory exists
|
|
293
|
+
if (!existsSync(resolvedPath)) {
|
|
294
|
+
return {
|
|
295
|
+
content: [{ type: 'text', text: `Directory not found: ${dirPath}` }],
|
|
296
|
+
details: { error: 'directory_not_found' },
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
const stats = statSync(resolvedPath);
|
|
300
|
+
// Check if it's actually a directory
|
|
301
|
+
if (!stats.isDirectory()) {
|
|
302
|
+
return {
|
|
303
|
+
content: [{ type: 'text', text: `Path is not a directory: ${dirPath}` }],
|
|
304
|
+
details: { error: 'not_a_directory' },
|
|
305
|
+
};
|
|
306
|
+
}
|
|
307
|
+
if (recursive) {
|
|
308
|
+
const entries = [];
|
|
309
|
+
function walk(dir, prefix = '') {
|
|
310
|
+
const items = readdirSync(dir);
|
|
311
|
+
for (const item of items) {
|
|
312
|
+
// Skip hidden files/directories
|
|
313
|
+
if (item.startsWith('.'))
|
|
314
|
+
continue;
|
|
315
|
+
const fullPath = join(dir, item);
|
|
316
|
+
const relPath = prefix ? `${prefix}/${item}` : item;
|
|
317
|
+
const itemStats = statSync(fullPath);
|
|
318
|
+
entries.push(`${relPath}${itemStats.isDirectory() ? '/' : ''}`);
|
|
319
|
+
if (itemStats.isDirectory()) {
|
|
320
|
+
walk(fullPath, relPath);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
walk(resolvedPath);
|
|
325
|
+
return {
|
|
326
|
+
content: [
|
|
327
|
+
{ type: 'text', text: `Directory: ${dirPath} (${entries.length} entries)` },
|
|
328
|
+
{ type: 'text', text: entries.join('\n') || '(empty)' },
|
|
329
|
+
],
|
|
330
|
+
details: {
|
|
331
|
+
path: dirPath,
|
|
332
|
+
count: entries.length,
|
|
333
|
+
recursive: true,
|
|
334
|
+
},
|
|
335
|
+
};
|
|
336
|
+
}
|
|
337
|
+
else {
|
|
338
|
+
const items = readdirSync(resolvedPath);
|
|
339
|
+
// Sort: directories first, then files
|
|
340
|
+
const sorted = items
|
|
341
|
+
.filter((item) => !item.startsWith('.'))
|
|
342
|
+
.sort((a, b) => {
|
|
343
|
+
const aPath = join(resolvedPath, a);
|
|
344
|
+
const bPath = join(resolvedPath, b);
|
|
345
|
+
const aIsDir = statSync(aPath).isDirectory();
|
|
346
|
+
const bIsDir = statSync(bPath).isDirectory();
|
|
347
|
+
if (aIsDir && !bIsDir)
|
|
348
|
+
return -1;
|
|
349
|
+
if (!aIsDir && bIsDir)
|
|
350
|
+
return 1;
|
|
351
|
+
return a.localeCompare(b);
|
|
352
|
+
});
|
|
353
|
+
const formatted = sorted
|
|
354
|
+
.map((item) => {
|
|
355
|
+
const fullPath = join(resolvedPath, item);
|
|
356
|
+
const isDir = statSync(fullPath).isDirectory();
|
|
357
|
+
return `${isDir ? '📁' : '📄'} ${item}${isDir ? '/' : ''}`;
|
|
358
|
+
})
|
|
359
|
+
.join('\n');
|
|
360
|
+
return {
|
|
361
|
+
content: [
|
|
362
|
+
{ type: 'text', text: `Directory: ${dirPath} (${sorted.length} items)` },
|
|
363
|
+
{ type: 'text', text: formatted || '(empty)' },
|
|
364
|
+
],
|
|
365
|
+
details: {
|
|
366
|
+
path: dirPath,
|
|
367
|
+
count: sorted.length,
|
|
368
|
+
recursive: false,
|
|
369
|
+
},
|
|
370
|
+
};
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
catch (error) {
|
|
374
|
+
return {
|
|
375
|
+
content: [{ type: 'text', text: `Error listing directory: ${error}` }],
|
|
376
|
+
details: { error: String(error) },
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
},
|
|
380
|
+
});
|
|
381
|
+
/**
|
|
382
|
+
* Tool: search_files
|
|
383
|
+
* Search for patterns in files
|
|
384
|
+
*/
|
|
385
|
+
pi.registerTool({
|
|
386
|
+
name: 'search_files',
|
|
387
|
+
description: 'Search for text patterns in files using glob patterns. Returns list of matching file paths.',
|
|
388
|
+
// @ts-ignore TSchema mismatch
|
|
389
|
+
parameters: {
|
|
390
|
+
type: 'object',
|
|
391
|
+
properties: {
|
|
392
|
+
pattern: {
|
|
393
|
+
type: 'string',
|
|
394
|
+
description: 'The text pattern to search for (supports regex)',
|
|
395
|
+
},
|
|
396
|
+
path: {
|
|
397
|
+
type: 'string',
|
|
398
|
+
description: 'The directory path to search in',
|
|
399
|
+
},
|
|
400
|
+
glob: {
|
|
401
|
+
type: 'string',
|
|
402
|
+
description: 'Glob pattern for file matching (default: "**/*")',
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
required: ['pattern', 'path'],
|
|
406
|
+
},
|
|
407
|
+
async execute(args) {
|
|
408
|
+
try {
|
|
409
|
+
const searchPattern = String(args.pattern);
|
|
410
|
+
const searchPath = String(args.path);
|
|
411
|
+
const globPattern = args.glob ? String(args.glob) : '**/*';
|
|
412
|
+
// Validate path
|
|
413
|
+
const pathCheck = validatePath(searchPath);
|
|
414
|
+
if (!pathCheck.valid) {
|
|
415
|
+
return {
|
|
416
|
+
content: [{ type: 'text', text: `Path validation failed: ${pathCheck.error}` }],
|
|
417
|
+
details: { error: pathCheck.error },
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
const resolvedPath = pathCheck.resolvedPath;
|
|
421
|
+
// Check if directory exists
|
|
422
|
+
if (!existsSync(resolvedPath)) {
|
|
423
|
+
return {
|
|
424
|
+
content: [{ type: 'text', text: `Directory not found: ${searchPath}` }],
|
|
425
|
+
details: { error: 'directory_not_found' },
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
// Find files matching glob pattern
|
|
429
|
+
const files = await glob(globPattern, {
|
|
430
|
+
cwd: resolvedPath,
|
|
431
|
+
absolute: true,
|
|
432
|
+
nodir: true,
|
|
433
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
|
|
434
|
+
});
|
|
435
|
+
// Search for pattern in files
|
|
436
|
+
const regex = new RegExp(searchPattern, 'i');
|
|
437
|
+
const matches = [];
|
|
438
|
+
for (const file of files.slice(0, 100)) {
|
|
439
|
+
// Limit to 100 files for performance
|
|
440
|
+
try {
|
|
441
|
+
const stats = statSync(file);
|
|
442
|
+
if (stats.size > MAX_FILE_SIZE)
|
|
443
|
+
continue;
|
|
444
|
+
const content = await readFile(file, 'utf-8');
|
|
445
|
+
const lines = content.split('\n');
|
|
446
|
+
for (let i = 0; i < lines.length; i++) {
|
|
447
|
+
if (regex.test(lines[i])) {
|
|
448
|
+
matches.push({
|
|
449
|
+
file: file.replace(resolvedPath + '/', ''),
|
|
450
|
+
line: i + 1,
|
|
451
|
+
content: lines[i].trim().substring(0, 100),
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
catch {
|
|
457
|
+
// Skip files that can't be read
|
|
458
|
+
continue;
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
// Group matches by file
|
|
462
|
+
const grouped = matches.reduce((acc, match) => {
|
|
463
|
+
if (!acc[match.file])
|
|
464
|
+
acc[match.file] = [];
|
|
465
|
+
acc[match.file].push(match);
|
|
466
|
+
return acc;
|
|
467
|
+
}, {});
|
|
468
|
+
const formatted = Object.entries(grouped)
|
|
469
|
+
.map(([file, fileMatches]) => {
|
|
470
|
+
const lines = fileMatches.map((m) => ` Line ${m.line}: ${m.content}`).join('\n');
|
|
471
|
+
return `${file}\n${lines}`;
|
|
472
|
+
})
|
|
473
|
+
.join('\n\n');
|
|
474
|
+
return {
|
|
475
|
+
content: [
|
|
476
|
+
{
|
|
477
|
+
type: 'text',
|
|
478
|
+
text: `Found ${matches.length} matches in ${Object.keys(grouped).length} files`,
|
|
479
|
+
},
|
|
480
|
+
{ type: 'text', text: formatted || 'No matches found' },
|
|
481
|
+
],
|
|
482
|
+
details: {
|
|
483
|
+
pattern: searchPattern,
|
|
484
|
+
filesScanned: files.length,
|
|
485
|
+
matchesFound: matches.length,
|
|
486
|
+
uniqueFiles: Object.keys(grouped).length,
|
|
487
|
+
},
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
catch (error) {
|
|
491
|
+
return {
|
|
492
|
+
content: [{ type: 'text', text: `Error searching files: ${error}` }],
|
|
493
|
+
details: { error: String(error) },
|
|
494
|
+
};
|
|
495
|
+
}
|
|
496
|
+
},
|
|
497
|
+
});
|
|
498
|
+
/**
|
|
499
|
+
* Tool: batch_edit
|
|
500
|
+
* Edit multiple files matching a pattern
|
|
501
|
+
*/
|
|
502
|
+
pi.registerTool({
|
|
503
|
+
name: 'batch_edit',
|
|
504
|
+
description: 'Find and replace text across multiple files matching a glob pattern. Returns list of edited files.',
|
|
505
|
+
// @ts-ignore TSchema mismatch
|
|
506
|
+
parameters: {
|
|
507
|
+
type: 'object',
|
|
508
|
+
properties: {
|
|
509
|
+
glob: {
|
|
510
|
+
type: 'string',
|
|
511
|
+
description: 'Glob pattern to match files (e.g., "**/*.ts")',
|
|
512
|
+
},
|
|
513
|
+
search: {
|
|
514
|
+
type: 'string',
|
|
515
|
+
description: 'The text to search for',
|
|
516
|
+
},
|
|
517
|
+
replace: {
|
|
518
|
+
type: 'string',
|
|
519
|
+
description: 'The replacement text',
|
|
520
|
+
},
|
|
521
|
+
regex: {
|
|
522
|
+
type: 'boolean',
|
|
523
|
+
description: 'If true, treat search as regex pattern (default: false)',
|
|
524
|
+
},
|
|
525
|
+
},
|
|
526
|
+
required: ['glob', 'search', 'replace'],
|
|
527
|
+
},
|
|
528
|
+
async execute(args) {
|
|
529
|
+
try {
|
|
530
|
+
const globPattern = String(args.glob);
|
|
531
|
+
const searchPattern = String(args.search);
|
|
532
|
+
const replacement = String(args.replace);
|
|
533
|
+
const useRegex = args.regex === true;
|
|
534
|
+
// Validate glob pattern (prevent directory traversal in glob)
|
|
535
|
+
if (globPattern.includes('..')) {
|
|
536
|
+
return {
|
|
537
|
+
content: [{ type: 'text', text: 'Invalid glob pattern: contains traversal sequence' }],
|
|
538
|
+
details: { error: 'invalid_glob' },
|
|
539
|
+
};
|
|
540
|
+
}
|
|
541
|
+
// Find matching files from the working directory
|
|
542
|
+
const files = await glob(globPattern, {
|
|
543
|
+
cwd: getWorkingDir(),
|
|
544
|
+
absolute: true,
|
|
545
|
+
nodir: true,
|
|
546
|
+
ignore: ['**/node_modules/**', '**/.git/**', '**/dist/**', '**/build/**'],
|
|
547
|
+
});
|
|
548
|
+
if (files.length === 0) {
|
|
549
|
+
return {
|
|
550
|
+
content: [{ type: 'text', text: 'No files matched the glob pattern' }],
|
|
551
|
+
details: { filesMatched: 0 },
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
const searchRegex = useRegex
|
|
555
|
+
? new RegExp(searchPattern, 'g')
|
|
556
|
+
: new RegExp(escapeRegExp(searchPattern), 'g');
|
|
557
|
+
const edited = [];
|
|
558
|
+
const errors = [];
|
|
559
|
+
for (const file of files) {
|
|
560
|
+
try {
|
|
561
|
+
// Check file size
|
|
562
|
+
const stats = statSync(file);
|
|
563
|
+
if (stats.size > MAX_FILE_SIZE) {
|
|
564
|
+
errors.push(`${file}: File too large`);
|
|
565
|
+
continue;
|
|
566
|
+
}
|
|
567
|
+
const content = await readFile(file, 'utf-8');
|
|
568
|
+
if (searchRegex.test(content)) {
|
|
569
|
+
// Reset regex lastIndex for replace
|
|
570
|
+
searchRegex.lastIndex = 0;
|
|
571
|
+
const newContent = content.replace(searchRegex, replacement);
|
|
572
|
+
await writeFileAsync(file, newContent, 'utf-8');
|
|
573
|
+
edited.push(file);
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
catch (err) {
|
|
577
|
+
errors.push(`${file}: ${err}`);
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
const resultText = [
|
|
581
|
+
`Edited ${edited.length} of ${files.length} files`,
|
|
582
|
+
'',
|
|
583
|
+
...edited.map((f) => ` ✓ ${f}`),
|
|
584
|
+
errors.length > 0 ? '' : '',
|
|
585
|
+
...(errors.length > 0 ? [`Errors (${errors.length}):`, ...errors.map((e) => ` ✗ ${e}`)] : []),
|
|
586
|
+
].join('\n');
|
|
587
|
+
return {
|
|
588
|
+
content: [{ type: 'text', text: resultText }],
|
|
589
|
+
details: {
|
|
590
|
+
filesMatched: files.length,
|
|
591
|
+
filesEdited: edited.length,
|
|
592
|
+
errors: errors.length,
|
|
593
|
+
edited,
|
|
594
|
+
errorList: errors,
|
|
595
|
+
},
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
catch (error) {
|
|
599
|
+
return {
|
|
600
|
+
content: [{ type: 'text', text: `Error in batch edit: ${error}` }],
|
|
601
|
+
details: { error: String(error) },
|
|
602
|
+
};
|
|
603
|
+
}
|
|
604
|
+
},
|
|
605
|
+
});
|
|
606
|
+
/**
|
|
607
|
+
* Tool: shell
|
|
608
|
+
* Execute shell command with safety checks
|
|
609
|
+
*/
|
|
610
|
+
pi.registerTool({
|
|
611
|
+
name: 'shell',
|
|
612
|
+
description: 'Execute a shell command using Bun. Returns command output. Has safety checks for dangerous commands.',
|
|
613
|
+
// @ts-ignore TSchema mismatch
|
|
614
|
+
parameters: {
|
|
615
|
+
type: 'object',
|
|
616
|
+
properties: {
|
|
617
|
+
command: {
|
|
618
|
+
type: 'string',
|
|
619
|
+
description: 'The shell command to execute',
|
|
620
|
+
},
|
|
621
|
+
cwd: {
|
|
622
|
+
type: 'string',
|
|
623
|
+
description: 'Working directory for the command (default: current directory)',
|
|
624
|
+
},
|
|
625
|
+
timeout: {
|
|
626
|
+
type: 'number',
|
|
627
|
+
description: 'Timeout in milliseconds (default: 30000)',
|
|
628
|
+
},
|
|
629
|
+
},
|
|
630
|
+
required: ['command'],
|
|
631
|
+
},
|
|
632
|
+
async execute(args) {
|
|
633
|
+
try {
|
|
634
|
+
const command = String(args.command);
|
|
635
|
+
const cwd = args.cwd ? String(args.cwd) : getWorkingDir();
|
|
636
|
+
const timeout = typeof args.timeout === 'number' ? args.timeout : SHELL_TIMEOUT_DEFAULT;
|
|
637
|
+
// Validate cwd
|
|
638
|
+
const cwdCheck = validatePath(cwd);
|
|
639
|
+
if (!cwdCheck.valid) {
|
|
640
|
+
return {
|
|
641
|
+
content: [{ type: 'text', text: `CWD validation failed: ${cwdCheck.error}` }],
|
|
642
|
+
details: { error: cwdCheck.error },
|
|
643
|
+
};
|
|
644
|
+
}
|
|
645
|
+
// Security check
|
|
646
|
+
const safety = isCommandSafe(command);
|
|
647
|
+
if (!safety.safe) {
|
|
648
|
+
return {
|
|
649
|
+
content: [{ type: 'text', text: `Command blocked: ${safety.reason}` }],
|
|
650
|
+
details: { error: 'command_blocked', reason: safety.reason },
|
|
651
|
+
};
|
|
652
|
+
}
|
|
653
|
+
// Execute command using Bun shell with working directory
|
|
654
|
+
const result = await $ `sh -c ${command}`.cwd(cwd).text();
|
|
655
|
+
return {
|
|
656
|
+
content: [
|
|
657
|
+
{ type: 'text', text: result || '(Command executed successfully with no output)' },
|
|
658
|
+
],
|
|
659
|
+
details: {
|
|
660
|
+
command,
|
|
661
|
+
cwd,
|
|
662
|
+
timeout,
|
|
663
|
+
},
|
|
664
|
+
};
|
|
665
|
+
}
|
|
666
|
+
catch (error) {
|
|
667
|
+
return {
|
|
668
|
+
content: [{ type: 'text', text: `Command failed: ${error}` }],
|
|
669
|
+
details: { error: String(error), command: args.command },
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
},
|
|
673
|
+
});
|
|
674
|
+
// Register status bar item
|
|
675
|
+
// @ts-ignore ExtensionAPI property (not present in all runtimes)
|
|
676
|
+
if (typeof pi.registerStatusBarItem === 'function') {
|
|
677
|
+
// @ts-ignore ExtensionAPI property
|
|
678
|
+
pi.registerStatusBarItem('fileops', {
|
|
679
|
+
render() {
|
|
680
|
+
return '📁 FileOps Ready';
|
|
681
|
+
},
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
// Log initialization
|
|
685
|
+
console.log('[FileOps] Extension loaded with 6 tools:');
|
|
686
|
+
console.log(' - read_file_with_line_numbers');
|
|
687
|
+
console.log(' - write_file');
|
|
688
|
+
console.log(' - list_directory');
|
|
689
|
+
console.log(' - search_files');
|
|
690
|
+
console.log(' - batch_edit');
|
|
691
|
+
console.log(' - shell');
|
|
692
|
+
}
|
|
693
|
+
/**
|
|
694
|
+
* Escape special regex characters
|
|
695
|
+
*/
|
|
696
|
+
function escapeRegExp(string) {
|
|
697
|
+
return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
698
|
+
}
|
|
699
|
+
//# sourceMappingURL=fileops-extension.js.map
|