@opencoven/coven-code 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.
Files changed (86) hide show
  1. package/README.md +145 -0
  2. package/bin/coven-code-sdk.mjs +12 -0
  3. package/bin/coven-code.mjs +19 -0
  4. package/docs/CLI.md +192 -0
  5. package/docs/CONFIGURATION.md +107 -0
  6. package/docs/DEVELOPMENT.md +104 -0
  7. package/docs/DOGFOOD-PROTOCOL.md +263 -0
  8. package/docs/MCP-SKILLS-PLUGINS.md +127 -0
  9. package/docs/README.md +38 -0
  10. package/docs/RELEASE.md +33 -0
  11. package/docs/SDK.md +107 -0
  12. package/docs/superpowers/plans/2026-05-25-coven-code-panel-tui.md +904 -0
  13. package/docs/superpowers/plans/2026-05-25-coven-code-rebrand.md +670 -0
  14. package/docs/superpowers/specs/2026-05-25-coven-code-panel-tui-design.md +235 -0
  15. package/docs/superpowers/specs/2026-05-26-slash-first-tui-review.md +63 -0
  16. package/package.json +36 -0
  17. package/src/agent/lane.mjs +136 -0
  18. package/src/agent/local.mjs +95 -0
  19. package/src/cli/dispatch.mjs +66 -0
  20. package/src/cli/execute.mjs +588 -0
  21. package/src/cli/help.mjs +58 -0
  22. package/src/cli/interactive-core.mjs +302 -0
  23. package/src/cli/notifications.mjs +13 -0
  24. package/src/cli/parse.mjs +83 -0
  25. package/src/cli/reasoning.mjs +45 -0
  26. package/src/cli/refs.mjs +162 -0
  27. package/src/cli/repl.mjs +61 -0
  28. package/src/cli/slash-commands.mjs +357 -0
  29. package/src/cli/stream-json.mjs +116 -0
  30. package/src/cli/tui.mjs +757 -0
  31. package/src/commands/agents.mjs +53 -0
  32. package/src/commands/config.mjs +27 -0
  33. package/src/commands/ide.mjs +17 -0
  34. package/src/commands/login.mjs +84 -0
  35. package/src/commands/mcp.mjs +176 -0
  36. package/src/commands/permissions.mjs +328 -0
  37. package/src/commands/plugins.mjs +86 -0
  38. package/src/commands/review.mjs +74 -0
  39. package/src/commands/skill.mjs +23 -0
  40. package/src/commands/threads.mjs +165 -0
  41. package/src/commands/tools.mjs +77 -0
  42. package/src/commands/update.mjs +31 -0
  43. package/src/commands/usage.mjs +34 -0
  44. package/src/constants.mjs +46 -0
  45. package/src/main.mjs +87 -0
  46. package/src/mcp/discover.mjs +154 -0
  47. package/src/mcp/permissions.mjs +52 -0
  48. package/src/mcp/probe.mjs +424 -0
  49. package/src/mcp/registry.mjs +96 -0
  50. package/src/plugins/discover.mjs +880 -0
  51. package/src/sdk-install.mjs +187 -0
  52. package/src/sdk.mjs +314 -0
  53. package/src/settings/load.mjs +134 -0
  54. package/src/settings/paths.mjs +101 -0
  55. package/src/skills/builtin/building-skills/SKILL.md +20 -0
  56. package/src/skills/discover.mjs +95 -0
  57. package/src/threads/store.mjs +176 -0
  58. package/src/tools/builtin/bash.mjs +110 -0
  59. package/src/tools/builtin/create-file.mjs +66 -0
  60. package/src/tools/builtin/edit-file.mjs +76 -0
  61. package/src/tools/builtin/finder.mjs +73 -0
  62. package/src/tools/builtin/glob.mjs +74 -0
  63. package/src/tools/builtin/grep.mjs +82 -0
  64. package/src/tools/builtin/index.mjs +83 -0
  65. package/src/tools/builtin/librarian.mjs +97 -0
  66. package/src/tools/builtin/look-at.mjs +92 -0
  67. package/src/tools/builtin/mcp.mjs +51 -0
  68. package/src/tools/builtin/mermaid.mjs +59 -0
  69. package/src/tools/builtin/oracle.mjs +56 -0
  70. package/src/tools/builtin/painter.mjs +81 -0
  71. package/src/tools/builtin/plugin-tool.mjs +53 -0
  72. package/src/tools/builtin/read-mcp-resource.mjs +63 -0
  73. package/src/tools/builtin/read-web-page.mjs +72 -0
  74. package/src/tools/builtin/read.mjs +59 -0
  75. package/src/tools/builtin/runtime.mjs +215 -0
  76. package/src/tools/builtin/task.mjs +63 -0
  77. package/src/tools/builtin/toolbox-tool.mjs +57 -0
  78. package/src/tools/builtin/undo-edit.mjs +97 -0
  79. package/src/tools/builtin/web-search.mjs +128 -0
  80. package/src/tools/toolbox.mjs +273 -0
  81. package/src/util/fs.mjs +13 -0
  82. package/src/util/glob.mjs +46 -0
  83. package/src/util/html.mjs +21 -0
  84. package/src/util/media.mjs +13 -0
  85. package/src/util/shell.mjs +24 -0
  86. package/src/util/table.mjs +11 -0
@@ -0,0 +1,73 @@
1
+ import { resolvePermissionDecision } from '../../commands/permissions.mjs';
2
+ import { runPluginEventHandlers } from '../../plugins/discover.mjs';
3
+ import { listThreads, threadSearchText } from '../../threads/store.mjs';
4
+ import { isToolDisabled } from '../toolbox.mjs';
5
+ import {
6
+ applyToolCallDecision,
7
+ createToolUseID,
8
+ permissionDeniedOutput,
9
+ pluginTextToolRunResult,
10
+ pluginToolCallEvent,
11
+ pluginToolResultEvent,
12
+ pluginToolUseBlock,
13
+ toolCallDecisionToolRun,
14
+ validateToolCallDecision,
15
+ } from './runtime.mjs';
16
+
17
+ export const TOOL_NAME = 'finder';
18
+
19
+ export async function executePromptFinderToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '', toolName = TOOL_NAME) {
20
+ if (isToolDisabled(toolName, 'built-in', parsed)) return { output: `Tool disabled: ${toolName}` };
21
+ request = { ...request, flags: normalizeFinderInput(request.flags) };
22
+ if (!request.flags.query) return { output: `${toolName} requires --query` };
23
+ const toolUseID = createToolUseID();
24
+ const callDecision = await runPluginEventHandlers(
25
+ plugins.handlers['tool.call'],
26
+ pluginToolCallEvent(toolName, request.flags, threadId, toolUseID),
27
+ validateToolCallDecision,
28
+ );
29
+ const callResult = applyToolCallDecision(toolName, request, callDecision);
30
+ if (callResult.output) return toolCallDecisionToolRun(toolName, request.flags, toolUseID, callResult);
31
+ request = { ...callResult.request, flags: normalizeFinderInput(callResult.request.flags) };
32
+ const decision = resolvePermissionDecision(toolName, request.flags, parsed, { threadId });
33
+ if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
34
+ return {
35
+ output: permissionDeniedOutput(toolName, decision),
36
+ permissionDenials: [{ tool: toolName, action: decision.action, reason: 'permission' }],
37
+ };
38
+ }
39
+ const output = findThreadsBuiltin(request.flags.query, request.flags.limit);
40
+ const resultDecision = await runPluginEventHandlers(
41
+ plugins.handlers['tool.result'],
42
+ pluginToolResultEvent(toolName, request.flags, 'done', output, threadId, toolUseID),
43
+ );
44
+ return {
45
+ ...pluginTextToolRunResult(resultDecision, output),
46
+ toolUse: pluginToolUseBlock(toolName, request.flags, toolUseID),
47
+ };
48
+ }
49
+
50
+ function normalizeFinderInput(input = {}) {
51
+ const query = input.query ?? input.q ?? input.search ?? input.task ?? input.file;
52
+ const limit = Number.parseInt(String(input.limit ?? input.count ?? 10), 10);
53
+ return {
54
+ ...input,
55
+ query: query ? String(query) : '',
56
+ limit: Number.isFinite(limit) && limit > 0 ? Math.min(limit, 50) : 10,
57
+ };
58
+ }
59
+
60
+ function findThreadsBuiltin(query, limit = 10) {
61
+ const normalizedQuery = query.toLowerCase();
62
+ const rows = listThreads()
63
+ .filter((thread) => threadSearchText(thread).toLowerCase().includes(normalizedQuery))
64
+ .slice(0, limit)
65
+ .map((thread) => [
66
+ thread.id,
67
+ thread.archived ? 'archived' : 'active',
68
+ thread.visibility ?? 'private',
69
+ thread.labels?.length ? thread.labels.join(',') : '-',
70
+ thread.title,
71
+ ].join('\t'));
72
+ return rows.length ? rows.join('\n') : `No threads found for ${query}`;
73
+ }
@@ -0,0 +1,74 @@
1
+ import path from 'node:path';
2
+ import { resolvePermissionDecision } from '../../commands/permissions.mjs';
3
+ import { runPluginEventHandlers } from '../../plugins/discover.mjs';
4
+ import { globToRegex, walkFiles } from '../../util/glob.mjs';
5
+ import { isToolDisabled } from '../toolbox.mjs';
6
+ import {
7
+ applyToolCallDecision,
8
+ createToolUseID,
9
+ permissionDeniedOutput,
10
+ pluginTextToolRunResult,
11
+ pluginToolCallEvent,
12
+ pluginToolResultEvent,
13
+ pluginToolUseBlock,
14
+ relativeToolPath,
15
+ toolCallDecisionToolRun,
16
+ validateToolCallDecision,
17
+ } from './runtime.mjs';
18
+
19
+ export const TOOL_NAME = 'glob';
20
+
21
+ export async function executePromptGlobToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
22
+ if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
23
+ request = { ...request, flags: normalizeGlobInput(request.flags) };
24
+ if (!request.flags.pattern) return { output: 'glob requires --pattern' };
25
+ const toolUseID = createToolUseID();
26
+ const callDecision = await runPluginEventHandlers(
27
+ plugins.handlers['tool.call'],
28
+ pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
29
+ validateToolCallDecision,
30
+ );
31
+ const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
32
+ if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
33
+ request = { ...callResult.request, flags: normalizeGlobInput(callResult.request.flags) };
34
+ const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
35
+ if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
36
+ return {
37
+ output: permissionDeniedOutput(TOOL_NAME, decision),
38
+ permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
39
+ };
40
+ }
41
+ const output = globBuiltinFiles(request.flags.pattern, request.flags.path);
42
+ const resultDecision = await runPluginEventHandlers(
43
+ plugins.handlers['tool.result'],
44
+ pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
45
+ );
46
+ return {
47
+ ...pluginTextToolRunResult(resultDecision, output),
48
+ toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
49
+ };
50
+ }
51
+
52
+ function normalizeGlobInput(input = {}) {
53
+ const pattern = input.pattern ?? input.glob ?? input.query;
54
+ const searchPath = input.path ?? input.dir ?? input.cwd ?? '.';
55
+ return {
56
+ ...input,
57
+ pattern: pattern ? String(pattern) : '',
58
+ path: searchPath ? String(searchPath) : '.',
59
+ };
60
+ }
61
+
62
+ function globBuiltinFiles(pattern, searchPath = '.') {
63
+ const root = path.isAbsolute(searchPath) ? searchPath : path.resolve(process.cwd(), searchPath);
64
+ const absolutePattern = path.isAbsolute(pattern) ? path.normalize(pattern) : path.normalize(path.join(root, pattern));
65
+ const matchers = [globToRegex(absolutePattern)];
66
+ if (!path.isAbsolute(pattern) && pattern.startsWith('**/')) {
67
+ matchers.push(globToRegex(path.normalize(path.join(root, pattern.slice('**/'.length)))));
68
+ }
69
+ return walkFiles(root)
70
+ .filter((filePath) => matchers.some((matcher) => matcher.test(path.normalize(filePath))))
71
+ .map(relativeToolPath)
72
+ .sort()
73
+ .join('\n');
74
+ }
@@ -0,0 +1,82 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolvePermissionDecision } from '../../commands/permissions.mjs';
4
+ import { runPluginEventHandlers } from '../../plugins/discover.mjs';
5
+ import { walkFiles } from '../../util/glob.mjs';
6
+ import { isToolDisabled } from '../toolbox.mjs';
7
+ import {
8
+ applyToolCallDecision,
9
+ createToolUseID,
10
+ permissionDeniedOutput,
11
+ pluginTextToolRunResult,
12
+ pluginToolCallEvent,
13
+ pluginToolResultEvent,
14
+ pluginToolUseBlock,
15
+ relativeToolPath,
16
+ toolCallDecisionToolRun,
17
+ validateToolCallDecision,
18
+ } from './runtime.mjs';
19
+
20
+ export const TOOL_NAME = 'Grep';
21
+
22
+ export async function executePromptGrepToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
23
+ if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
24
+ request = { ...request, flags: normalizeGrepInput(request.flags) };
25
+ if (!request.flags.pattern) return { output: 'Grep requires --pattern' };
26
+ const toolUseID = createToolUseID();
27
+ const callDecision = await runPluginEventHandlers(
28
+ plugins.handlers['tool.call'],
29
+ pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
30
+ validateToolCallDecision,
31
+ );
32
+ const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
33
+ if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
34
+ request = { ...callResult.request, flags: normalizeGrepInput(callResult.request.flags) };
35
+ const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
36
+ if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
37
+ return {
38
+ output: permissionDeniedOutput(TOOL_NAME, decision),
39
+ permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
40
+ };
41
+ }
42
+ const output = grepBuiltinFiles(request.flags.pattern, request.flags.path);
43
+ const resultDecision = await runPluginEventHandlers(
44
+ plugins.handlers['tool.result'],
45
+ pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
46
+ );
47
+ return {
48
+ ...pluginTextToolRunResult(resultDecision, output),
49
+ toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
50
+ };
51
+ }
52
+
53
+ function normalizeGrepInput(input = {}) {
54
+ const pattern = input.pattern ?? input.regex ?? input.query;
55
+ const searchPath = input.path ?? input.dir ?? input.cwd ?? '.';
56
+ return {
57
+ ...input,
58
+ pattern: pattern ? String(pattern) : '',
59
+ path: searchPath ? String(searchPath) : '.',
60
+ };
61
+ }
62
+
63
+ function grepBuiltinFiles(pattern, searchPath = '.') {
64
+ const root = path.isAbsolute(searchPath) ? searchPath : path.resolve(process.cwd(), searchPath);
65
+ const files = walkFiles(root).sort();
66
+ const re = new RegExp(pattern);
67
+ const matches = [];
68
+ for (const filePath of files) {
69
+ let content = '';
70
+ try {
71
+ content = readFileSync(filePath, 'utf8');
72
+ } catch {
73
+ continue;
74
+ }
75
+ const lines = content.split(/\r?\n/);
76
+ for (let index = 0; index < lines.length; index += 1) {
77
+ if (!re.test(lines[index])) continue;
78
+ matches.push(`${relativeToolPath(filePath)}:${index + 1}:${lines[index]}`);
79
+ }
80
+ }
81
+ return matches.join('\n');
82
+ }
@@ -0,0 +1,83 @@
1
+ import { loadPlugins } from '../../plugins/discover.mjs';
2
+ import { parseToolUseArgs } from '../toolbox.mjs';
3
+ import { splitShellWords } from '../../util/shell.mjs';
4
+ import { executePromptBashToolRequest } from './bash.mjs';
5
+ import { executePromptCreateFileToolRequest } from './create-file.mjs';
6
+ import { executePromptEditFileToolRequest } from './edit-file.mjs';
7
+ import { executePromptFinderToolRequest } from './finder.mjs';
8
+ import { executePromptGlobToolRequest } from './glob.mjs';
9
+ import { executePromptGrepToolRequest } from './grep.mjs';
10
+ import { executePromptLibrarianToolRequest } from './librarian.mjs';
11
+ import { executePromptLookAtToolRequest } from './look-at.mjs';
12
+ import { executePromptMcpToolRequest } from './mcp.mjs';
13
+ import { executePromptMermaidToolRequest } from './mermaid.mjs';
14
+ import { executePromptOracleToolRequest } from './oracle.mjs';
15
+ import { executePromptPainterToolRequest } from './painter.mjs';
16
+ import { executePromptPluginToolRequest } from './plugin-tool.mjs';
17
+ import { executePromptReadMcpResourceToolRequest } from './read-mcp-resource.mjs';
18
+ import { executePromptReadToolRequest } from './read.mjs';
19
+ import { executePromptReadWebPageToolRequest } from './read-web-page.mjs';
20
+ import { executePromptTaskToolRequest } from './task.mjs';
21
+ import { executePromptToolboxToolRequest } from './toolbox-tool.mjs';
22
+ import { executePromptUndoEditToolRequest } from './undo-edit.mjs';
23
+ import { executePromptWebSearchToolRequest } from './web-search.mjs';
24
+
25
+ const BUILTIN_DISPATCH = new Map([
26
+ ['Bash', (request, stdin, parsed, plugins, threadId) =>
27
+ executePromptBashToolRequest(request, stdin, parsed, plugins, threadId)],
28
+ ['Read', (request, _stdin, parsed, plugins, threadId) =>
29
+ executePromptReadToolRequest(request, parsed, plugins, threadId)],
30
+ ['Grep', (request, _stdin, parsed, plugins, threadId) =>
31
+ executePromptGrepToolRequest(request, parsed, plugins, threadId)],
32
+ ['glob', (request, _stdin, parsed, plugins, threadId) =>
33
+ executePromptGlobToolRequest(request, parsed, plugins, threadId)],
34
+ ['create_file', (request, _stdin, parsed, plugins, threadId) =>
35
+ executePromptCreateFileToolRequest(request, parsed, plugins, threadId)],
36
+ ['edit_file', (request, _stdin, parsed, plugins, threadId) =>
37
+ executePromptEditFileToolRequest(request, parsed, plugins, threadId)],
38
+ ['undo_edit', (request, _stdin, parsed, plugins, threadId) =>
39
+ executePromptUndoEditToolRequest(request, parsed, plugins, threadId)],
40
+ ['Task', (request, _stdin, parsed, plugins, threadId) =>
41
+ executePromptTaskToolRequest(request, parsed, plugins, threadId)],
42
+ ['oracle', (request, _stdin, parsed, plugins, threadId) =>
43
+ executePromptOracleToolRequest(request, parsed, plugins, threadId)],
44
+ ['librarian', (request, _stdin, parsed, plugins, threadId) =>
45
+ executePromptLibrarianToolRequest(request, parsed, plugins, threadId)],
46
+ ['painter', (request, _stdin, parsed, plugins, threadId) =>
47
+ executePromptPainterToolRequest(request, parsed, plugins, threadId)],
48
+ ['mermaid', (request, _stdin, parsed, plugins, threadId) =>
49
+ executePromptMermaidToolRequest(request, parsed, plugins, threadId)],
50
+ ['look_at', (request, _stdin, parsed, plugins, threadId) =>
51
+ executePromptLookAtToolRequest(request, parsed, plugins, threadId)],
52
+ ['web_search', (request, _stdin, parsed, plugins, threadId) =>
53
+ executePromptWebSearchToolRequest(request, parsed, plugins, threadId)],
54
+ ['read_web_page', (request, _stdin, parsed, plugins, threadId) =>
55
+ executePromptReadWebPageToolRequest(request, parsed, plugins, threadId)],
56
+ ['find_thread', (request, _stdin, parsed, plugins, threadId) =>
57
+ executePromptFinderToolRequest(request, parsed, plugins, threadId, 'find_thread')],
58
+ ['finder', (request, _stdin, parsed, plugins, threadId) =>
59
+ executePromptFinderToolRequest(request, parsed, plugins, threadId)],
60
+ ['read_mcp_resource', (request, _stdin, parsed, plugins, threadId) =>
61
+ executePromptReadMcpResourceToolRequest(request, parsed, plugins, threadId)],
62
+ ]);
63
+
64
+ export async function executePromptToolRequest(prompt, stdin, threadId, parsed = {}, plugins) {
65
+ const request = parsePromptToolRequest(prompt);
66
+ if (!request) return undefined;
67
+ const resolvedPlugins = plugins ?? await loadPlugins(process.cwd());
68
+ if (request.toolName.startsWith('mcp__')) {
69
+ return executePromptMcpToolRequest(request, parsed, threadId, resolvedPlugins);
70
+ }
71
+ const builtin = BUILTIN_DISPATCH.get(request.toolName);
72
+ if (builtin) return builtin(request, stdin, parsed, resolvedPlugins, threadId);
73
+ const pluginTool = resolvedPlugins.tools.find((entry) => entry.name === request.toolName);
74
+ if (pluginTool) return executePromptPluginToolRequest(pluginTool, request, parsed, resolvedPlugins, threadId);
75
+ return executePromptToolboxToolRequest(request, stdin, parsed, resolvedPlugins, threadId);
76
+ }
77
+
78
+ export function parsePromptToolRequest(prompt) {
79
+ const match = prompt.match(/\b(?:use|run|call)\s+([A-Za-z0-9_-]+(?:__[A-Za-z0-9_-]+)?)([^\r\n]*)/i);
80
+ if (!match) return undefined;
81
+ const useArgs = parseToolUseArgs([match[1], ...splitShellWords(match[2] ?? '')]);
82
+ return { toolName: useArgs.toolName, flags: useArgs.flags };
83
+ }
@@ -0,0 +1,97 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { resolvePermissionDecision } from '../../commands/permissions.mjs';
4
+ import { runPluginEventHandlers } from '../../plugins/discover.mjs';
5
+ import { walkFiles } from '../../util/glob.mjs';
6
+ import { isToolDisabled } from '../toolbox.mjs';
7
+ import {
8
+ applyToolCallDecision,
9
+ createToolUseID,
10
+ permissionDeniedOutput,
11
+ pluginTextToolRunResult,
12
+ pluginToolCallEvent,
13
+ pluginToolResultEvent,
14
+ pluginToolUseBlock,
15
+ toolCallDecisionToolRun,
16
+ validateToolCallDecision,
17
+ } from './runtime.mjs';
18
+
19
+ export const TOOL_NAME = 'librarian';
20
+
21
+ export async function executePromptLibrarianToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
22
+ if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
23
+ request = { ...request, flags: normalizeLibrarianInput(request.flags) };
24
+ if (!request.flags.query) return { output: 'librarian requires --query' };
25
+ const toolUseID = createToolUseID();
26
+ const callDecision = await runPluginEventHandlers(
27
+ plugins.handlers['tool.call'],
28
+ pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
29
+ validateToolCallDecision,
30
+ );
31
+ const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
32
+ if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
33
+ request = { ...callResult.request, flags: normalizeLibrarianInput(callResult.request.flags) };
34
+ const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
35
+ if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
36
+ return {
37
+ output: permissionDeniedOutput(TOOL_NAME, decision),
38
+ permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
39
+ };
40
+ }
41
+ const output = searchWorkspaceForLibrarian(request.flags);
42
+ const resultDecision = await runPluginEventHandlers(
43
+ plugins.handlers['tool.result'],
44
+ pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
45
+ );
46
+ return {
47
+ ...pluginTextToolRunResult(resultDecision, output),
48
+ toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
49
+ };
50
+ }
51
+
52
+ function normalizeLibrarianInput(input = {}) {
53
+ const query = input.query ?? input.q ?? input.prompt ?? input.question ?? input.topic;
54
+ const limit = Number.parseInt(String(input.limit ?? 5), 10);
55
+ return {
56
+ ...input,
57
+ query: query ? String(query) : '',
58
+ limit: Number.isFinite(limit) && limit > 0 ? Math.min(limit, 20) : 5,
59
+ };
60
+ }
61
+
62
+ function searchWorkspaceForLibrarian(input) {
63
+ const matches = findWorkspaceTextMatches(input.query, input.limit);
64
+ return [
65
+ `Librarian search: ${input.query}`,
66
+ 'scope: current workspace',
67
+ 'matches:',
68
+ ...(matches.length ? matches.map(formatLibrarianMatch) : ['- no local matches found']),
69
+ ].join('\n');
70
+ }
71
+
72
+ function findWorkspaceTextMatches(query, limit) {
73
+ const needle = query.toLowerCase();
74
+ const root = process.cwd();
75
+ return walkFiles(root)
76
+ .map((filePath) => path.relative(root, filePath))
77
+ .filter((relativePath) => !relativePath.split(path.sep).some((part) => part === '.git' || part === 'node_modules'))
78
+ .sort()
79
+ .flatMap((relativePath) => {
80
+ let text;
81
+ try {
82
+ text = readFileSync(path.join(root, relativePath), 'utf8');
83
+ } catch {
84
+ return [];
85
+ }
86
+ return text.split(/\r?\n/).flatMap((line, index) => (
87
+ line.toLowerCase().includes(needle)
88
+ ? [{ path: relativePath, line: index + 1, text: line.trim() }]
89
+ : []
90
+ ));
91
+ })
92
+ .slice(0, limit);
93
+ }
94
+
95
+ function formatLibrarianMatch(match) {
96
+ return `- ${match.path}:${match.line}: ${match.text}`;
97
+ }
@@ -0,0 +1,92 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import path from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { resolvePermissionDecision } from '../../commands/permissions.mjs';
5
+ import { runPluginEventHandlers } from '../../plugins/discover.mjs';
6
+ import { detectImageMediaType } from '../../util/media.mjs';
7
+ import { isToolDisabled } from '../toolbox.mjs';
8
+ import {
9
+ applyToolCallDecision,
10
+ createToolUseID,
11
+ permissionDeniedOutput,
12
+ pluginTextToolRunResult,
13
+ pluginToolCallEvent,
14
+ pluginToolResultEvent,
15
+ pluginToolUseBlock,
16
+ toolCallDecisionToolRun,
17
+ validateToolCallDecision,
18
+ } from './runtime.mjs';
19
+
20
+ export const TOOL_NAME = 'look_at';
21
+
22
+ export async function executePromptLookAtToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
23
+ if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
24
+ request = { ...request, flags: normalizeLookAtInput(request.flags) };
25
+ if (!request.flags.path) return { output: 'look_at requires --path' };
26
+ const toolUseID = createToolUseID();
27
+ const callDecision = await runPluginEventHandlers(
28
+ plugins.handlers['tool.call'],
29
+ pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
30
+ validateToolCallDecision,
31
+ );
32
+ const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
33
+ if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
34
+ request = { ...callResult.request, flags: normalizeLookAtInput(callResult.request.flags) };
35
+ const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
36
+ if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
37
+ return {
38
+ output: permissionDeniedOutput(TOOL_NAME, decision),
39
+ permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
40
+ };
41
+ }
42
+ const output = inspectLookAtMedia(request.flags);
43
+ const resultDecision = await runPluginEventHandlers(
44
+ plugins.handlers['tool.result'],
45
+ pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
46
+ );
47
+ return {
48
+ ...pluginTextToolRunResult(resultDecision, output),
49
+ toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
50
+ };
51
+ }
52
+
53
+ function normalizeLookAtInput(input = {}) {
54
+ const source = input.path ?? input.file ?? input.image ?? input.pdf ?? input.url;
55
+ const goal = input.goal ?? input.prompt ?? input.instructions ?? input.question ?? '';
56
+ return {
57
+ ...input,
58
+ path: source ? String(source) : '',
59
+ goal: String(goal),
60
+ };
61
+ }
62
+
63
+ function inspectLookAtMedia(input) {
64
+ try {
65
+ const resolved = resolveLookAtPath(input.path);
66
+ const raw = readFileSync(resolved);
67
+ return formatLookAtMediaResult(input.path, raw, input.goal);
68
+ } catch (error) {
69
+ return `Unable to look at ${input.path}: ${error.message}`;
70
+ }
71
+ }
72
+
73
+ function resolveLookAtPath(source) {
74
+ if (source.startsWith('file://')) return fileURLToPath(source);
75
+ return path.resolve(process.cwd(), source);
76
+ }
77
+
78
+ function formatLookAtMediaResult(source, raw, goal) {
79
+ return [
80
+ `Looked at: ${source}`,
81
+ `media_type: ${detectLookAtMediaType(raw)}`,
82
+ `bytes: ${raw.length}`,
83
+ `goal: ${goal || '(none)'}`,
84
+ ].join('\n');
85
+ }
86
+
87
+ function detectLookAtMediaType(raw) {
88
+ const imageType = detectImageMediaType(raw);
89
+ if (imageType) return imageType;
90
+ if (raw.subarray(0, 5).toString('ascii') === '%PDF-') return 'application/pdf';
91
+ return 'application/octet-stream';
92
+ }
@@ -0,0 +1,51 @@
1
+ import { resolvePermissionDecision } from '../../commands/permissions.mjs';
2
+ import { listConfiguredMcpServers } from '../../mcp/discover.mjs';
3
+ import { callMcpTool, isMcpToolIncluded, parseMcpToolName } from '../../mcp/probe.mjs';
4
+ import { runPluginEventHandlers } from '../../plugins/discover.mjs';
5
+ import { isToolDisabled } from '../toolbox.mjs';
6
+ import {
7
+ applyToolCallDecision,
8
+ createToolUseID,
9
+ permissionDeniedOutput,
10
+ pluginTextToolRunResult,
11
+ pluginToolCallEvent,
12
+ pluginToolResultEvent,
13
+ pluginToolUseBlock,
14
+ toolCallDecisionToolRun,
15
+ validateToolCallDecision,
16
+ } from './runtime.mjs';
17
+
18
+ export async function executePromptMcpToolRequest(request, parsed = {}, threadId = '', plugins = { handlers: {} }) {
19
+ const parsedName = parseMcpToolName(request.toolName);
20
+ if (!parsedName) return { output: `Unknown tool: ${request.toolName}` };
21
+ if (isToolDisabled(request.toolName, 'local-mcp', parsed)) return { output: `Tool disabled: ${request.toolName}` };
22
+ const toolUseID = createToolUseID();
23
+ const callDecision = await runPluginEventHandlers(
24
+ plugins.handlers['tool.call'],
25
+ pluginToolCallEvent(request.toolName, request.flags, threadId, toolUseID),
26
+ validateToolCallDecision,
27
+ );
28
+ const callResult = applyToolCallDecision(request.toolName, request, callDecision);
29
+ if (callResult.output) return toolCallDecisionToolRun(request.toolName, request.flags, toolUseID, callResult);
30
+ request = callResult.request;
31
+ const decision = resolvePermissionDecision(request.toolName, request.flags, parsed, { threadId });
32
+ if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
33
+ return {
34
+ output: permissionDeniedOutput(request.toolName, decision),
35
+ permissionDenials: [{ tool: request.toolName, action: decision.action, reason: 'permission' }],
36
+ };
37
+ }
38
+ const server = listConfiguredMcpServers(parsed)
39
+ .find((entry) => entry.name === parsedName.serverName && entry.status === 'approved');
40
+ if (!server) return { output: `Unknown MCP server: ${parsedName.serverName}` };
41
+ if (!isMcpToolIncluded(server.config, parsedName.toolName)) return { output: `Tool not available: ${request.toolName}` };
42
+ const output = await callMcpTool(server.config, parsedName.toolName, request.flags, server.name);
43
+ const resultDecision = await runPluginEventHandlers(
44
+ plugins.handlers['tool.result'],
45
+ pluginToolResultEvent(request.toolName, request.flags, 'done', output, threadId, toolUseID),
46
+ );
47
+ return {
48
+ ...pluginTextToolRunResult(resultDecision, output),
49
+ toolUse: pluginToolUseBlock(request.toolName, request.flags, toolUseID),
50
+ };
51
+ }
@@ -0,0 +1,59 @@
1
+ import { resolvePermissionDecision } from '../../commands/permissions.mjs';
2
+ import { runPluginEventHandlers } from '../../plugins/discover.mjs';
3
+ import { isToolDisabled } from '../toolbox.mjs';
4
+ import {
5
+ applyToolCallDecision,
6
+ createToolUseID,
7
+ permissionDeniedOutput,
8
+ pluginTextToolRunResult,
9
+ pluginToolCallEvent,
10
+ pluginToolResultEvent,
11
+ pluginToolUseBlock,
12
+ toolCallDecisionToolRun,
13
+ validateToolCallDecision,
14
+ } from './runtime.mjs';
15
+
16
+ export const TOOL_NAME = 'mermaid';
17
+
18
+ export async function executePromptMermaidToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
19
+ if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
20
+ request = { ...request, flags: normalizeMermaidInput(request.flags) };
21
+ if (!request.flags.code) return { output: 'mermaid requires --code' };
22
+ const toolUseID = createToolUseID();
23
+ const callDecision = await runPluginEventHandlers(
24
+ plugins.handlers['tool.call'],
25
+ pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
26
+ validateToolCallDecision,
27
+ );
28
+ const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
29
+ if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
30
+ request = { ...callResult.request, flags: normalizeMermaidInput(callResult.request.flags) };
31
+ const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
32
+ if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
33
+ return {
34
+ output: permissionDeniedOutput(TOOL_NAME, decision),
35
+ permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
36
+ };
37
+ }
38
+ const output = mermaidOutput(request.flags.code);
39
+ const resultDecision = await runPluginEventHandlers(
40
+ plugins.handlers['tool.result'],
41
+ pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
42
+ );
43
+ return {
44
+ ...pluginTextToolRunResult(resultDecision, output),
45
+ toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
46
+ };
47
+ }
48
+
49
+ function normalizeMermaidInput(input = {}) {
50
+ const code = input.code ?? input.diagram ?? input.mermaid ?? input.source;
51
+ return {
52
+ ...input,
53
+ code: code ? String(code) : '',
54
+ };
55
+ }
56
+
57
+ function mermaidOutput(code) {
58
+ return `\`\`\`mermaid\n${String(code).trim()}\n\`\`\``;
59
+ }
@@ -0,0 +1,56 @@
1
+ import { localAgentResponse } from '../../agent/local.mjs';
2
+ import { resolvePermissionDecision } from '../../commands/permissions.mjs';
3
+ import { runPluginEventHandlers } from '../../plugins/discover.mjs';
4
+ import { isToolDisabled } from '../toolbox.mjs';
5
+ import {
6
+ applyToolCallDecision,
7
+ createToolUseID,
8
+ permissionDeniedOutput,
9
+ pluginTextToolRunResult,
10
+ pluginToolCallEvent,
11
+ pluginToolResultEvent,
12
+ pluginToolUseBlock,
13
+ toolCallDecisionToolRun,
14
+ validateToolCallDecision,
15
+ } from './runtime.mjs';
16
+
17
+ export const TOOL_NAME = 'oracle';
18
+
19
+ export async function executePromptOracleToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
20
+ if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
21
+ request = { ...request, flags: normalizeOracleInput(request.flags) };
22
+ if (!request.flags.prompt) return { output: 'oracle requires --prompt' };
23
+ const toolUseID = createToolUseID();
24
+ const callDecision = await runPluginEventHandlers(
25
+ plugins.handlers['tool.call'],
26
+ pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
27
+ validateToolCallDecision,
28
+ );
29
+ const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
30
+ if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
31
+ request = { ...callResult.request, flags: normalizeOracleInput(callResult.request.flags) };
32
+ const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
33
+ if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
34
+ return {
35
+ output: permissionDeniedOutput(TOOL_NAME, decision),
36
+ permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
37
+ };
38
+ }
39
+ const output = `Oracle: ${localAgentResponse(request.flags.prompt, '')}`;
40
+ const resultDecision = await runPluginEventHandlers(
41
+ plugins.handlers['tool.result'],
42
+ pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
43
+ );
44
+ return {
45
+ ...pluginTextToolRunResult(resultDecision, output),
46
+ toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
47
+ };
48
+ }
49
+
50
+ function normalizeOracleInput(input = {}) {
51
+ const prompt = input.prompt ?? input.question ?? input.query ?? input.message;
52
+ return {
53
+ ...input,
54
+ prompt: prompt ? String(prompt) : '',
55
+ };
56
+ }