@zhijiewang/openharness 1.2.0 → 1.4.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.
@@ -0,0 +1,242 @@
1
+ /**
2
+ * Plugin Marketplace — discover, install, and manage plugins from curated registries.
3
+ *
4
+ * Marketplaces are JSON files listing available plugins with versions and sources.
5
+ * Plugins are downloaded and cached to ~/.oh/plugins/cache/ for security and versioning.
6
+ *
7
+ * Inspired by Claude Code's marketplace model.
8
+ */
9
+ import { readFileSync, writeFileSync, mkdirSync, existsSync, readdirSync, rmSync } from 'node:fs';
10
+ import { join, basename } from 'node:path';
11
+ import { homedir } from 'node:os';
12
+ import { execSync } from 'node:child_process';
13
+ const MARKETPLACE_DIR = join(homedir(), '.oh', 'marketplaces');
14
+ const PLUGIN_CACHE_DIR = join(homedir(), '.oh', 'plugins', 'cache');
15
+ const INSTALLED_PLUGINS_FILE = join(homedir(), '.oh', 'plugins', 'installed.json');
16
+ // ── Marketplace Management ──
17
+ /** Add a marketplace from a URL, GitHub repo, or local path */
18
+ export function addMarketplace(nameOrUrl) {
19
+ mkdirSync(MARKETPLACE_DIR, { recursive: true });
20
+ // Fetch marketplace.json
21
+ let data;
22
+ let marketplaceName;
23
+ if (nameOrUrl.startsWith('http')) {
24
+ // URL
25
+ try {
26
+ data = execSync(`curl -sL "${nameOrUrl}/marketplace.json"`, { encoding: 'utf-8', timeout: 10_000 });
27
+ marketplaceName = new URL(nameOrUrl).hostname;
28
+ }
29
+ catch {
30
+ return null;
31
+ }
32
+ }
33
+ else if (nameOrUrl.includes('/') && !nameOrUrl.startsWith('.')) {
34
+ // GitHub repo (owner/repo format)
35
+ try {
36
+ const url = `https://raw.githubusercontent.com/${nameOrUrl}/main/marketplace.json`;
37
+ data = execSync(`curl -sL "${url}"`, { encoding: 'utf-8', timeout: 10_000 });
38
+ marketplaceName = nameOrUrl.replace('/', '-');
39
+ }
40
+ catch {
41
+ return null;
42
+ }
43
+ }
44
+ else if (existsSync(join(nameOrUrl, 'marketplace.json'))) {
45
+ // Local path
46
+ data = readFileSync(join(nameOrUrl, 'marketplace.json'), 'utf-8');
47
+ marketplaceName = basename(nameOrUrl);
48
+ }
49
+ else {
50
+ return null;
51
+ }
52
+ try {
53
+ const marketplace = JSON.parse(data);
54
+ if (!marketplace.plugins || !Array.isArray(marketplace.plugins))
55
+ return null;
56
+ marketplace.name = marketplace.name ?? marketplaceName;
57
+ writeFileSync(join(MARKETPLACE_DIR, `${marketplace.name}.json`), JSON.stringify(marketplace, null, 2));
58
+ return marketplace;
59
+ }
60
+ catch {
61
+ return null;
62
+ }
63
+ }
64
+ /** Remove a marketplace */
65
+ export function removeMarketplace(name) {
66
+ const path = join(MARKETPLACE_DIR, `${name}.json`);
67
+ if (!existsSync(path))
68
+ return false;
69
+ try {
70
+ rmSync(path);
71
+ return true;
72
+ }
73
+ catch {
74
+ return false;
75
+ }
76
+ }
77
+ /** List all configured marketplaces */
78
+ export function listMarketplaces() {
79
+ if (!existsSync(MARKETPLACE_DIR))
80
+ return [];
81
+ return readdirSync(MARKETPLACE_DIR)
82
+ .filter(f => f.endsWith('.json'))
83
+ .map(f => {
84
+ try {
85
+ return JSON.parse(readFileSync(join(MARKETPLACE_DIR, f), 'utf-8'));
86
+ }
87
+ catch {
88
+ return null;
89
+ }
90
+ })
91
+ .filter((m) => m !== null);
92
+ }
93
+ /** Search all marketplaces for plugins matching a query */
94
+ export function searchMarketplace(query) {
95
+ const q = query.toLowerCase();
96
+ const results = [];
97
+ for (const mp of listMarketplaces()) {
98
+ for (const plugin of mp.plugins) {
99
+ if (plugin.name.toLowerCase().includes(q) ||
100
+ plugin.description.toLowerCase().includes(q) ||
101
+ plugin.keywords?.some(k => k.toLowerCase().includes(q))) {
102
+ results.push({ ...plugin, marketplace: mp.name });
103
+ }
104
+ }
105
+ }
106
+ return results;
107
+ }
108
+ // ── Plugin Installation ──
109
+ /** Install a plugin from a marketplace */
110
+ export function installPlugin(pluginName, marketplaceName) {
111
+ // Find the plugin in marketplaces
112
+ const marketplaces = listMarketplaces();
113
+ let entry = null;
114
+ let fromMarketplace = '';
115
+ for (const mp of marketplaces) {
116
+ if (marketplaceName && mp.name !== marketplaceName)
117
+ continue;
118
+ const found = mp.plugins.find(p => p.name === pluginName);
119
+ if (found) {
120
+ entry = found;
121
+ fromMarketplace = mp.name;
122
+ break;
123
+ }
124
+ }
125
+ if (!entry)
126
+ return null;
127
+ // Download to cache
128
+ const cacheDir = join(PLUGIN_CACHE_DIR, entry.name, entry.version);
129
+ mkdirSync(cacheDir, { recursive: true });
130
+ try {
131
+ switch (entry.source.type) {
132
+ case 'github': {
133
+ // Clone the repo to cache
134
+ execSync(`git clone --depth 1 "https://github.com/${entry.source.repo}.git" "${cacheDir}"`, { stdio: 'pipe', timeout: 30_000 });
135
+ break;
136
+ }
137
+ case 'npm': {
138
+ // Install npm package to cache
139
+ execSync(`npm pack "${entry.source.package}" --pack-destination "${cacheDir}"`, { stdio: 'pipe', timeout: 30_000 });
140
+ // Extract the tarball
141
+ const tgz = readdirSync(cacheDir).find(f => f.endsWith('.tgz'));
142
+ if (tgz) {
143
+ execSync(`tar xzf "${join(cacheDir, tgz)}" -C "${cacheDir}" --strip-components=1`, { stdio: 'pipe' });
144
+ }
145
+ break;
146
+ }
147
+ case 'url': {
148
+ execSync(`curl -sL "${entry.source.url}" -o "${join(cacheDir, 'plugin.tar.gz')}"`, { stdio: 'pipe', timeout: 30_000 });
149
+ execSync(`tar xzf "${join(cacheDir, 'plugin.tar.gz')}" -C "${cacheDir}"`, { stdio: 'pipe' });
150
+ break;
151
+ }
152
+ }
153
+ }
154
+ catch {
155
+ // Clean up failed install
156
+ try {
157
+ rmSync(cacheDir, { recursive: true });
158
+ }
159
+ catch { /* ignore */ }
160
+ return null;
161
+ }
162
+ // Record installation
163
+ const installed = {
164
+ name: entry.name,
165
+ version: entry.version,
166
+ marketplace: fromMarketplace,
167
+ installedAt: Date.now(),
168
+ cachePath: cacheDir,
169
+ };
170
+ saveInstalledPlugin(installed);
171
+ return installed;
172
+ }
173
+ /** Uninstall a plugin */
174
+ export function uninstallPlugin(name) {
175
+ const installed = getInstalledPlugins();
176
+ const plugin = installed.find(p => p.name === name);
177
+ if (!plugin)
178
+ return false;
179
+ // Remove from cache
180
+ try {
181
+ rmSync(plugin.cachePath, { recursive: true });
182
+ }
183
+ catch { /* ignore */ }
184
+ // Remove from installed list
185
+ const remaining = installed.filter(p => p.name !== name);
186
+ saveInstalledPluginList(remaining);
187
+ return true;
188
+ }
189
+ /** Get all installed plugins */
190
+ export function getInstalledPlugins() {
191
+ if (!existsSync(INSTALLED_PLUGINS_FILE))
192
+ return [];
193
+ try {
194
+ return JSON.parse(readFileSync(INSTALLED_PLUGINS_FILE, 'utf-8'));
195
+ }
196
+ catch {
197
+ return [];
198
+ }
199
+ }
200
+ function saveInstalledPlugin(plugin) {
201
+ const installed = getInstalledPlugins();
202
+ // Replace existing version
203
+ const idx = installed.findIndex(p => p.name === plugin.name);
204
+ if (idx >= 0)
205
+ installed[idx] = plugin;
206
+ else
207
+ installed.push(plugin);
208
+ saveInstalledPluginList(installed);
209
+ }
210
+ function saveInstalledPluginList(plugins) {
211
+ const dir = join(homedir(), '.oh', 'plugins');
212
+ mkdirSync(dir, { recursive: true });
213
+ writeFileSync(INSTALLED_PLUGINS_FILE, JSON.stringify(plugins, null, 2));
214
+ }
215
+ // ── Formatting ──
216
+ /** Format marketplace entries for display */
217
+ export function formatMarketplaceSearch(results) {
218
+ if (results.length === 0)
219
+ return 'No plugins found.';
220
+ const lines = [`Found ${results.length} plugin(s):\n`];
221
+ for (const r of results) {
222
+ lines.push(` ${r.name}@${r.version} [${r.marketplace}]`);
223
+ lines.push(` ${r.description}`);
224
+ if (r.author)
225
+ lines.push(` by ${r.author}`);
226
+ lines.push('');
227
+ }
228
+ lines.push('Install with: /plugin install <name>');
229
+ return lines.join('\n');
230
+ }
231
+ /** Format installed plugins for display */
232
+ export function formatInstalledPlugins(plugins) {
233
+ if (plugins.length === 0)
234
+ return 'No plugins installed from marketplaces.';
235
+ const lines = [`Installed Plugins (${plugins.length}):\n`];
236
+ for (const p of plugins) {
237
+ const age = Math.round((Date.now() - p.installedAt) / (1000 * 60 * 60 * 24));
238
+ lines.push(` ${p.name}@${p.version} [${p.marketplace}] ${age}d ago`);
239
+ }
240
+ return lines.join('\n');
241
+ }
242
+ //# sourceMappingURL=marketplace.js.map
@@ -42,7 +42,7 @@ export type AgentTeamConfig = {
42
42
  tools?: string[];
43
43
  }>;
44
44
  };
45
- /** Discover all available skills from project + global dirs */
45
+ /** Discover all available skills from project + global dirs + installed plugins */
46
46
  export declare function discoverSkills(): SkillMetadata[];
47
47
  /** Find a skill by name (case-insensitive) */
48
48
  export declare function findSkill(name: string): SkillMetadata | null;
@@ -67,11 +67,25 @@ function loadSkillsFromDir(dir, source) {
67
67
  })
68
68
  .filter((s) => s !== null);
69
69
  }
70
- /** Discover all available skills from project + global dirs */
70
+ /** Discover all available skills from project + global dirs + installed plugins */
71
71
  export function discoverSkills() {
72
72
  const skills = [];
73
73
  skills.push(...loadSkillsFromDir(PROJECT_SKILLS_DIR, 'project'));
74
74
  skills.push(...loadSkillsFromDir(GLOBAL_SKILLS_DIR, 'global'));
75
+ // Load skills from installed marketplace plugins (namespaced as plugin-name:skill-name)
76
+ try {
77
+ const { getInstalledPlugins } = require('./marketplace.js');
78
+ for (const plugin of getInstalledPlugins()) {
79
+ const pluginSkillsDir = join(plugin.cachePath, 'skills');
80
+ const pluginSkills = loadSkillsFromDir(pluginSkillsDir, 'plugin');
81
+ // Namespace: prefix skill name with plugin name
82
+ for (const skill of pluginSkills) {
83
+ skill.name = `${plugin.name}:${skill.name}`;
84
+ }
85
+ skills.push(...pluginSkills);
86
+ }
87
+ }
88
+ catch { /* marketplace module may not be loaded yet */ }
75
89
  return skills;
76
90
  }
77
91
  /** Find a skill by name (case-insensitive) */
@@ -65,13 +65,28 @@ export function loadRules(projectPath) {
65
65
  if (content)
66
66
  rules.push(content);
67
67
  }
68
- // 4. Project rules/*.md
68
+ // 4. Project rules/*.md (with optional path-scoped filtering)
69
69
  const rulesDir = join(root, ".oh", "rules");
70
70
  if (existsSync(rulesDir)) {
71
71
  for (const file of readdirSync(rulesDir).filter((f) => f.endsWith(".md")).sort()) {
72
- const content = readSafe(join(rulesDir, file));
73
- if (content)
74
- rules.push(content);
72
+ const raw = readSafe(join(rulesDir, file));
73
+ if (!raw)
74
+ continue;
75
+ // Check for paths frontmatter: only include if matching current context
76
+ const pathsMatch = raw.match(/^---\n[\s\S]*?^paths:\s*(.+)$/m);
77
+ if (pathsMatch) {
78
+ // Path-scoped rule — strip frontmatter and only include if glob matches
79
+ const pattern = pathsMatch[1].trim();
80
+ const fmEnd = raw.indexOf('---', raw.indexOf('---') + 3);
81
+ const content = fmEnd > 0 ? raw.slice(fmEnd + 3).trim() : raw;
82
+ if (content && matchesPathGlob(root, pattern)) {
83
+ rules.push(content);
84
+ }
85
+ }
86
+ else {
87
+ // No paths restriction — always include
88
+ rules.push(raw);
89
+ }
75
90
  }
76
91
  }
77
92
  // 5. CLAUDE.local.md (personal overrides, typically gitignored)
@@ -110,4 +125,17 @@ function readSafe(path) {
110
125
  return "";
111
126
  }
112
127
  }
128
+ /**
129
+ * Check if any file in the project matches a glob pattern.
130
+ * Simple implementation: checks if the pattern directory exists.
131
+ * For `src/api/**`, checks if `src/api/` exists.
132
+ */
133
+ function matchesPathGlob(root, pattern) {
134
+ // Extract the directory portion before any wildcard
135
+ const dirPart = pattern.split('*')[0].replace(/\/+$/, '');
136
+ if (!dirPart)
137
+ return true; // Pattern like "**/*.ts" matches everything
138
+ const fullDir = join(root, dirPart);
139
+ return existsSync(fullDir);
140
+ }
113
141
  //# sourceMappingURL=rules.js.map
@@ -69,14 +69,30 @@ export async function handleUserInput(input, ctx) {
69
69
  }
70
70
  // Normal prompt — add user message
71
71
  messages = [...messages, createUserMessage(input)];
72
- // Resolve @mentions
72
+ // Resolve @mentions — local files first, then MCP resources
73
73
  let resolvedInput = input;
74
- const mentionPattern = /@(\w[\w.-]*)/g;
74
+ const mentionPattern = /@([\w][\w./-]*)/g;
75
75
  const mentions = [...input.matchAll(mentionPattern)].map(m => m[1]);
76
76
  const companionName = ctx.companionConfig?.soul?.name?.toLowerCase();
77
77
  for (const mention of mentions) {
78
78
  if (companionName && mention.toLowerCase() === companionName)
79
79
  continue;
80
+ // Try local file first (supports paths like @src/main.ts, @README.md)
81
+ try {
82
+ const { existsSync, readFileSync } = await import('node:fs');
83
+ const { resolve } = await import('node:path');
84
+ const filePath = resolve(process.cwd(), mention);
85
+ if (existsSync(filePath)) {
86
+ const content = readFileSync(filePath, 'utf-8');
87
+ const truncated = content.length > 10_000
88
+ ? content.slice(0, 10_000) + '\n[...truncated]'
89
+ : content;
90
+ resolvedInput += `\n\n[File @${mention}]:\n${truncated}`;
91
+ continue;
92
+ }
93
+ }
94
+ catch { /* ignore */ }
95
+ // Fall back to MCP resource
80
96
  try {
81
97
  const content = await resolveMcpMention(mention);
82
98
  if (content)
package/dist/main.js CHANGED
@@ -233,6 +233,7 @@ program
233
233
  .option("--fork <id>", "Fork (branch) from an existing session")
234
234
  .option("--light", "Use light theme")
235
235
  .option("--output-format <format>", "Output format for -p mode (text, json, stream-json)", "text")
236
+ .option("--json-schema <schema>", "Constrain output to match a JSON schema (headless mode)")
236
237
  .action(async (opts) => {
237
238
  // Load saved config as defaults (env vars + CLI flags override)
238
239
  const savedConfig = readOhConfig();
@@ -4,6 +4,7 @@
4
4
  */
5
5
  import { createUserMessage } from "../types/message.js";
6
6
  import { defaultEstimateTokens } from "../providers/base.js";
7
+ import { emitHook } from "../harness/hooks.js";
7
8
  const DEFAULT_KEEP_LAST = 10;
8
9
  /**
9
10
  * Semantic importance scoring for messages.
@@ -61,6 +62,7 @@ export function estimateMessagesTokens(messages, estimateTokens = (t) => Math.ce
61
62
  export function compressMessages(messages, targetTokens) {
62
63
  if (messages.length <= 2)
63
64
  return messages;
65
+ emitHook("preCompact", {});
64
66
  const result = [...messages];
65
67
  const keepLast = DEFAULT_KEEP_LAST;
66
68
  // MicroCompact: Truncate long tool results and assistant messages
@@ -114,12 +116,14 @@ export function compressMessages(messages, targetTokens) {
114
116
  validCallIds.add(tc.id);
115
117
  }
116
118
  }
117
- return result.filter((msg) => {
119
+ const filtered = result.filter((msg) => {
118
120
  if (msg.role !== "tool")
119
121
  return true;
120
122
  return (msg.toolResults?.length ?? 0) > 0 &&
121
123
  msg.toolResults.every((tr) => validCallIds.has(tr.callId));
122
124
  });
125
+ emitHook("postCompact", {});
126
+ return filtered;
123
127
  }
124
128
  /**
125
129
  * LLM-assisted summarization of older messages.
@@ -85,6 +85,13 @@ export async function executeSingleTool(toolCall, tools, context, permissionMode
85
85
  toolArgs: JSON.stringify(toolCall.arguments).slice(0, 1000),
86
86
  toolOutput: result.output.slice(0, 1000),
87
87
  });
88
+ // Emit fileChanged hook for file-modifying tools
89
+ if (!result.isError && ['Edit', 'Write', 'MultiEdit'].includes(tool.name)) {
90
+ const filePaths = getAffectedFiles(tool.name, parsed.data);
91
+ for (const fp of filePaths) {
92
+ emitHook("fileChanged", { filePath: fp, toolName: tool.name });
93
+ }
94
+ }
88
95
  // Verification loop: auto-run lint/typecheck after file-modifying tools
89
96
  let verificationSuffix = '';
90
97
  if (!result.isError && ['Edit', 'Write', 'MultiEdit'].includes(tool.name)) {
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import { createWorktree, removeWorktree, hasWorktreeChanges, isGitRepo } from "../../git/index.js";
3
+ import { emitHook } from "../../harness/hooks.js";
3
4
  const inputSchema = z.object({
4
5
  prompt: z.string(),
5
6
  description: z.string().optional(),
@@ -80,9 +81,11 @@ export const AgentTool = {
80
81
  maxTurns: 20,
81
82
  abortSignal: context.abortSignal,
82
83
  };
84
+ const agentId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
85
+ emitHook("subagentStart", { agentId, toolName: input.subagent_type ?? 'general' });
83
86
  // Background execution: start agent and return immediately
84
87
  if (input.run_in_background) {
85
- const bgId = Date.now().toString(36) + Math.random().toString(36).slice(2, 6);
88
+ const bgId = agentId;
86
89
  const runAgent = async () => {
87
90
  let finalText = "";
88
91
  const originalCwd = process.cwd();
@@ -190,6 +193,7 @@ export const AgentTool = {
190
193
  }
191
194
  }
192
195
  }
196
+ emitHook("subagentStop", { agentId });
193
197
  return { output: finalText || "(sub-agent completed with no text output)", isError: false };
194
198
  },
195
199
  prompt() {
@@ -2,17 +2,17 @@ import { z } from "zod";
2
2
  import type { Tool } from "../../Tool.js";
3
3
  declare const inputSchema: z.ZodObject<{
4
4
  file_path: z.ZodString;
5
- action: z.ZodDefault<z.ZodEnum<["diagnostics", "definition", "references"]>>;
5
+ action: z.ZodDefault<z.ZodEnum<["diagnostics", "definition", "references", "hover"]>>;
6
6
  line: z.ZodOptional<z.ZodNumber>;
7
7
  character: z.ZodOptional<z.ZodNumber>;
8
8
  }, "strip", z.ZodTypeAny, {
9
- action: "diagnostics" | "definition" | "references";
10
9
  file_path: string;
10
+ action: "diagnostics" | "definition" | "references" | "hover";
11
11
  line?: number | undefined;
12
12
  character?: number | undefined;
13
13
  }, {
14
14
  file_path: string;
15
- action?: "diagnostics" | "definition" | "references" | undefined;
15
+ action?: "diagnostics" | "definition" | "references" | "hover" | undefined;
16
16
  line?: number | undefined;
17
17
  character?: number | undefined;
18
18
  }>;
@@ -2,8 +2,8 @@ import { z } from "zod";
2
2
  import { LspClient } from "../../lsp/client.js";
3
3
  const inputSchema = z.object({
4
4
  file_path: z.string().describe("Absolute path to the file to check"),
5
- action: z.enum(["diagnostics", "definition", "references"]).default("diagnostics")
6
- .describe("Action: diagnostics (errors/warnings), definition (go-to-def), references (find-refs)"),
5
+ action: z.enum(["diagnostics", "definition", "references", "hover"]).default("diagnostics")
6
+ .describe("Action: diagnostics (errors/warnings), definition (go-to-def), references (find-refs), hover (type info)"),
7
7
  line: z.number().optional().describe("Line number (0-indexed) for definition/references"),
8
8
  character: z.number().optional().describe("Column number (0-indexed) for definition/references"),
9
9
  });
@@ -16,6 +16,12 @@ function getLspCommand(filePath) {
16
16
  if (filePath.endsWith('.py')) {
17
17
  return { command: 'pylsp', args: [] };
18
18
  }
19
+ if (filePath.endsWith('.go')) {
20
+ return { command: 'gopls', args: ['serve'] };
21
+ }
22
+ if (filePath.endsWith('.rs')) {
23
+ return { command: 'rust-analyzer', args: [] };
24
+ }
19
25
  return null;
20
26
  }
21
27
  async function getClient(filePath, workingDir) {
@@ -84,6 +90,28 @@ export const DiagnosticsTool = {
84
90
  const lines = refs.map(r => `${r.uri.replace('file://', '')}:${r.range.start.line + 1}:${r.range.start.character}`);
85
91
  return { output: `${refs.length} reference(s):\n${lines.join('\n')}`, isError: false };
86
92
  }
93
+ if (input.action === "hover") {
94
+ if (input.line === undefined || input.character === undefined) {
95
+ return { output: "line and character are required for hover.", isError: true };
96
+ }
97
+ await client.openFile(input.file_path);
98
+ // Hover uses textDocument/hover which returns MarkupContent
99
+ try {
100
+ const result = await client.send('textDocument/hover', {
101
+ textDocument: { uri: `file://${input.file_path.replace(/\\/g, '/')}` },
102
+ position: { line: input.line, character: input.character },
103
+ });
104
+ if (!result || !result.contents)
105
+ return { output: "No hover information.", isError: false };
106
+ const content = typeof result.contents === 'string'
107
+ ? result.contents
108
+ : result.contents.value ?? JSON.stringify(result.contents);
109
+ return { output: content, isError: false };
110
+ }
111
+ catch {
112
+ return { output: "Hover not supported by this language server.", isError: false };
113
+ }
114
+ }
87
115
  return { output: `Unknown action: ${input.action}`, isError: true };
88
116
  }
89
117
  catch (err) {
@@ -94,15 +122,16 @@ export const DiagnosticsTool = {
94
122
  }
95
123
  },
96
124
  prompt() {
97
- return `Get code intelligence from the language server. Actions:
125
+ return `Get code intelligence from the language server. Supports TypeScript, JavaScript, Python, Go, and Rust. Actions:
98
126
  - diagnostics: Get errors and warnings for a file
99
- - definition: Go to definition of a symbol at a given position (requires line, character)
100
- - references: Find all references to a symbol at a given position (requires line, character)
127
+ - definition: Go to definition of a symbol at a given position
128
+ - references: Find all references to a symbol at a given position
129
+ - hover: Get type information and documentation for a symbol
101
130
  Parameters:
102
131
  - file_path (string, required): Absolute path to the file
103
- - action (string): "diagnostics" | "definition" | "references" (default: diagnostics)
104
- - line (number, optional): 0-indexed line for definition/references
105
- - character (number, optional): 0-indexed column for definition/references`;
132
+ - action (string): "diagnostics" | "definition" | "references" | "hover" (default: diagnostics)
133
+ - line (number, optional): 0-indexed line for definition/references/hover
134
+ - character (number, optional): 0-indexed column for definition/references/hover`;
106
135
  },
107
136
  };
108
137
  //# sourceMappingURL=index.js.map
@@ -0,0 +1,21 @@
1
+ import { z } from "zod";
2
+ import type { Tool } from "../../Tool.js";
3
+ declare const inputSchema: z.ZodObject<{
4
+ command: z.ZodString;
5
+ pattern: z.ZodOptional<z.ZodString>;
6
+ timeout: z.ZodOptional<z.ZodNumber>;
7
+ maxLines: z.ZodOptional<z.ZodNumber>;
8
+ }, "strip", z.ZodTypeAny, {
9
+ command: string;
10
+ pattern?: string | undefined;
11
+ timeout?: number | undefined;
12
+ maxLines?: number | undefined;
13
+ }, {
14
+ command: string;
15
+ pattern?: string | undefined;
16
+ timeout?: number | undefined;
17
+ maxLines?: number | undefined;
18
+ }>;
19
+ export declare const MonitorTool: Tool<typeof inputSchema>;
20
+ export {};
21
+ //# sourceMappingURL=index.d.ts.map