@opencoven/coven-code 0.0.4 → 0.0.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +29 -130
- package/bin/coven-code +26 -0
- package/install.js +117 -0
- package/package.json +26 -23
- package/bin/coven-code-sdk.mjs +0 -12
- package/bin/coven-code.mjs +0 -19
- package/docs/CLI.md +0 -256
- package/docs/CONFIGURATION.md +0 -107
- package/docs/DEMO.md +0 -453
- package/docs/DEVELOPMENT.md +0 -104
- package/docs/DOGFOOD-PROTOCOL.md +0 -263
- package/docs/MCP-SKILLS-PLUGINS.md +0 -127
- package/docs/README.md +0 -39
- package/docs/RELEASE.md +0 -33
- package/docs/SDK.md +0 -107
- package/docs/superpowers/plans/2026-05-25-coven-code-panel-tui.md +0 -904
- package/docs/superpowers/plans/2026-05-25-coven-code-rebrand.md +0 -670
- package/docs/superpowers/specs/2026-05-25-coven-code-panel-tui-design.md +0 -235
- package/docs/superpowers/specs/2026-05-26-slash-first-tui-review.md +0 -63
- package/src/agent/fixture.mjs +0 -95
- package/src/agent/lane.mjs +0 -136
- package/src/cli/dispatch.mjs +0 -66
- package/src/cli/execute.mjs +0 -452
- package/src/cli/help.mjs +0 -58
- package/src/cli/interactive-core.mjs +0 -28
- package/src/cli/interactive-io.mjs +0 -101
- package/src/cli/interactive-slash.mjs +0 -184
- package/src/cli/notifications.mjs +0 -13
- package/src/cli/parse.mjs +0 -83
- package/src/cli/reasoning.mjs +0 -45
- package/src/cli/refs.mjs +0 -162
- package/src/cli/repl.mjs +0 -60
- package/src/cli/slash-commands.mjs +0 -375
- package/src/cli/stream-json.mjs +0 -225
- package/src/cli/tui-actions.mjs +0 -72
- package/src/cli/tui-blessed.mjs +0 -198
- package/src/cli/tui-keys.mjs +0 -80
- package/src/cli/tui-lane.mjs +0 -73
- package/src/cli/tui-render.mjs +0 -169
- package/src/cli/tui-submit.mjs +0 -82
- package/src/cli/tui.mjs +0 -174
- package/src/commands/agents.mjs +0 -53
- package/src/commands/config.mjs +0 -27
- package/src/commands/ide.mjs +0 -17
- package/src/commands/login.mjs +0 -84
- package/src/commands/mcp.mjs +0 -176
- package/src/commands/permissions-eval.mjs +0 -122
- package/src/commands/permissions-rules.mjs +0 -53
- package/src/commands/permissions-text.mjs +0 -112
- package/src/commands/permissions.mjs +0 -62
- package/src/commands/plugins.mjs +0 -86
- package/src/commands/review.mjs +0 -74
- package/src/commands/skill.mjs +0 -23
- package/src/commands/threads.mjs +0 -165
- package/src/commands/tools.mjs +0 -77
- package/src/commands/update.mjs +0 -31
- package/src/commands/usage.mjs +0 -34
- package/src/constants.mjs +0 -52
- package/src/main.mjs +0 -87
- package/src/mcp/discover.mjs +0 -154
- package/src/mcp/local.mjs +0 -55
- package/src/mcp/parsers.mjs +0 -46
- package/src/mcp/permissions.mjs +0 -52
- package/src/mcp/probe.mjs +0 -85
- package/src/mcp/registry.mjs +0 -96
- package/src/mcp/remote-oauth.mjs +0 -55
- package/src/mcp/remote-session.mjs +0 -54
- package/src/mcp/remote-sse.mjs +0 -82
- package/src/mcp/remote.mjs +0 -74
- package/src/plugins/api.mjs +0 -187
- package/src/plugins/configuration.mjs +0 -124
- package/src/plugins/discover.mjs +0 -84
- package/src/plugins/helpers.mjs +0 -187
- package/src/plugins/subsystems.mjs +0 -198
- package/src/plugins/validators.mjs +0 -142
- package/src/sdk-execute.mjs +0 -82
- package/src/sdk-install.mjs +0 -187
- package/src/sdk-settings.mjs +0 -88
- package/src/sdk.mjs +0 -163
- package/src/settings/load.mjs +0 -134
- package/src/settings/paths.mjs +0 -101
- package/src/skills/builtin/building-skills/SKILL.md +0 -20
- package/src/skills/discover.mjs +0 -95
- package/src/threads/store.mjs +0 -176
- package/src/tools/builtin/bash.mjs +0 -110
- package/src/tools/builtin/create-file.mjs +0 -66
- package/src/tools/builtin/edit-file.mjs +0 -76
- package/src/tools/builtin/finder.mjs +0 -73
- package/src/tools/builtin/glob.mjs +0 -74
- package/src/tools/builtin/grep.mjs +0 -82
- package/src/tools/builtin/index.mjs +0 -83
- package/src/tools/builtin/librarian.mjs +0 -97
- package/src/tools/builtin/look-at.mjs +0 -92
- package/src/tools/builtin/mcp.mjs +0 -51
- package/src/tools/builtin/mermaid.mjs +0 -59
- package/src/tools/builtin/oracle.mjs +0 -56
- package/src/tools/builtin/painter.mjs +0 -81
- package/src/tools/builtin/plugin-tool.mjs +0 -53
- package/src/tools/builtin/read-mcp-resource.mjs +0 -63
- package/src/tools/builtin/read-web-page.mjs +0 -72
- package/src/tools/builtin/read.mjs +0 -59
- package/src/tools/builtin/runtime-content.mjs +0 -31
- package/src/tools/builtin/runtime-decisions.mjs +0 -115
- package/src/tools/builtin/runtime.mjs +0 -85
- package/src/tools/builtin/task.mjs +0 -63
- package/src/tools/builtin/toolbox-tool.mjs +0 -57
- package/src/tools/builtin/undo-edit.mjs +0 -97
- package/src/tools/builtin/web-search.mjs +0 -128
- package/src/tools/toolbox.mjs +0 -273
- package/src/util/fs.mjs +0 -13
- package/src/util/glob.mjs +0 -46
- package/src/util/html.mjs +0 -21
- package/src/util/media.mjs +0 -13
- package/src/util/shell.mjs +0 -24
- package/src/util/table.mjs +0 -11
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { resolvePermissionDecision } from '../../commands/permissions.mjs';
|
|
2
|
-
import { runPluginEventHandlers } from '../../plugins/discover.mjs';
|
|
3
|
-
import { decodeHtmlText } from '../../util/html.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 = 'web_search';
|
|
18
|
-
|
|
19
|
-
export async function executePromptWebSearchToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
|
|
20
|
-
if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
|
|
21
|
-
request = { ...request, flags: normalizeWebSearchInput(request.flags) };
|
|
22
|
-
if (!request.flags.query) return { output: 'web_search requires --query' };
|
|
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: normalizeWebSearchInput(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 = await searchWebBuiltin(request.flags.query, request.flags.limit);
|
|
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 normalizeWebSearchInput(input = {}) {
|
|
51
|
-
const query = input.query ?? input.q ?? input.search;
|
|
52
|
-
const limit = Number.parseInt(String(input.limit ?? input.count ?? 5), 10);
|
|
53
|
-
return {
|
|
54
|
-
...input,
|
|
55
|
-
query: query ? String(query) : '',
|
|
56
|
-
limit: Number.isFinite(limit) && limit > 0 ? Math.min(limit, 10) : 5,
|
|
57
|
-
};
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
async function searchWebBuiltin(query, limit = 5) {
|
|
61
|
-
const fixture = webSearchFixtureResults(limit);
|
|
62
|
-
if (fixture) return formatWebSearchResults(fixture, query);
|
|
63
|
-
|
|
64
|
-
try {
|
|
65
|
-
const url = `https://html.duckduckgo.com/html/?${new URLSearchParams({ q: query }).toString()}`;
|
|
66
|
-
const response = await fetch(url, {
|
|
67
|
-
headers: { 'user-agent': 'coven-code/0.0.0' },
|
|
68
|
-
});
|
|
69
|
-
if (!response.ok) return `Web search failed for ${query}: HTTP ${response.status}`;
|
|
70
|
-
const results = parseDuckDuckGoHtml(await response.text()).slice(0, limit);
|
|
71
|
-
return formatWebSearchResults(results, query);
|
|
72
|
-
} catch (error) {
|
|
73
|
-
return `Web search failed for ${query}: ${error.message}`;
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
function webSearchFixtureResults(limit) {
|
|
78
|
-
const fixtureJson = process.env.COVEN_CODE_WEB_SEARCH_RESULTS_JSON;
|
|
79
|
-
if (!fixtureJson) return undefined;
|
|
80
|
-
try {
|
|
81
|
-
const parsed = JSON.parse(fixtureJson);
|
|
82
|
-
return Array.isArray(parsed) ? parsed.slice(0, limit) : [];
|
|
83
|
-
} catch {
|
|
84
|
-
return [];
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function parseDuckDuckGoHtml(html) {
|
|
89
|
-
const blocks = html.match(/<div[^>]+class="[^"]*result[^"]*"[\s\S]*?(?=<div[^>]+class="[^"]*result[^"]*"|<\/body>)/g) ?? [];
|
|
90
|
-
return blocks
|
|
91
|
-
.map((block) => {
|
|
92
|
-
const link = block.match(/<a[^>]+class="[^"]*result__a[^"]*"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/);
|
|
93
|
-
if (!link) return undefined;
|
|
94
|
-
const snippet = block.match(/<a[^>]+class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/a>/)
|
|
95
|
-
?? block.match(/<div[^>]+class="[^"]*result__snippet[^"]*"[^>]*>([\s\S]*?)<\/div>/);
|
|
96
|
-
return {
|
|
97
|
-
title: cleanHtmlText(link[2]),
|
|
98
|
-
url: cleanDuckDuckGoUrl(decodeHtmlText(link[1])),
|
|
99
|
-
snippet: snippet ? cleanHtmlText(snippet[1]) : '',
|
|
100
|
-
};
|
|
101
|
-
})
|
|
102
|
-
.filter(Boolean);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
function cleanDuckDuckGoUrl(url) {
|
|
106
|
-
try {
|
|
107
|
-
const parsed = new URL(url);
|
|
108
|
-
const redirected = parsed.searchParams.get('uddg');
|
|
109
|
-
return redirected ? decodeURIComponent(redirected) : url;
|
|
110
|
-
} catch {
|
|
111
|
-
return url;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
function cleanHtmlText(value = '') {
|
|
116
|
-
return decodeHtmlText(value.replace(/<[^>]+>/g, ' ').replace(/\s+/g, ' ').trim());
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function formatWebSearchResults(results = [], query = '') {
|
|
120
|
-
if (results.length === 0) return `No web search results found for ${query}`;
|
|
121
|
-
return results
|
|
122
|
-
.map((result, index) => [
|
|
123
|
-
`${index + 1}. ${result.title ?? 'Untitled'}`,
|
|
124
|
-
result.url ?? '',
|
|
125
|
-
result.snippet ?? '',
|
|
126
|
-
].filter(Boolean).join('\n'))
|
|
127
|
-
.join('\n\n');
|
|
128
|
-
}
|
package/src/tools/toolbox.mjs
DELETED
|
@@ -1,273 +0,0 @@
|
|
|
1
|
-
import { spawnSync } from 'node:child_process';
|
|
2
|
-
import { randomUUID } from 'node:crypto';
|
|
3
|
-
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
4
|
-
import path from 'node:path';
|
|
5
|
-
import { BUILTIN_TOOLS } from '../constants.mjs';
|
|
6
|
-
import { toolsDirs } from '../settings/paths.mjs';
|
|
7
|
-
import { readEffectiveSettings } from '../settings/load.mjs';
|
|
8
|
-
import { globMatch } from '../util/glob.mjs';
|
|
9
|
-
|
|
10
|
-
export function listToolboxTools(parsed = {}) {
|
|
11
|
-
const seen = new Set();
|
|
12
|
-
const tools = [];
|
|
13
|
-
for (const dir of toolsDirs(parsed)) {
|
|
14
|
-
if (!existsSync(dir)) continue;
|
|
15
|
-
for (const entry of readdirSync(dir)) {
|
|
16
|
-
const filePath = path.join(dir, entry);
|
|
17
|
-
if (!statSync(filePath).isFile()) continue;
|
|
18
|
-
const name = `tb__${path.basename(filePath)}`;
|
|
19
|
-
if (seen.has(name)) continue;
|
|
20
|
-
seen.add(name);
|
|
21
|
-
const schema = describeToolboxTool(filePath);
|
|
22
|
-
tools.push({ name, path: filePath, description: schema.description, schema });
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
return tools;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export function isToolDisabled(name, kind, parsed = {}) {
|
|
29
|
-
const settings = readEffectiveSettings(parsed);
|
|
30
|
-
const enabled = settings['covenCode.tools.enable'];
|
|
31
|
-
if (Array.isArray(enabled) && !enabled.some((pattern) => typeof pattern === 'string' && globMatch(pattern, name))) {
|
|
32
|
-
return true;
|
|
33
|
-
}
|
|
34
|
-
const disabled = settings['covenCode.tools.disable'];
|
|
35
|
-
if (!Array.isArray(disabled)) return false;
|
|
36
|
-
return disabled.some((pattern) => {
|
|
37
|
-
if (typeof pattern !== 'string') return false;
|
|
38
|
-
if (pattern.startsWith('builtin:')) {
|
|
39
|
-
return kind === 'built-in' && globMatch(pattern.slice('builtin:'.length), name);
|
|
40
|
-
}
|
|
41
|
-
return globMatch(pattern, name);
|
|
42
|
-
});
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
export function toolKindForName(name) {
|
|
46
|
-
if (BUILTIN_TOOLS.some(([builtinName]) => builtinName === name)) return 'built-in';
|
|
47
|
-
if (name.startsWith('tb__')) return 'toolbox';
|
|
48
|
-
if (name.startsWith('mcp__')) return 'local-mcp';
|
|
49
|
-
return '';
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
function describeToolboxTool(filePath) {
|
|
53
|
-
const result = spawnSync(filePath, {
|
|
54
|
-
env: { ...process.env, TOOLBOX_ACTION: 'describe', AGENT: 'coven-code' },
|
|
55
|
-
encoding: 'utf8',
|
|
56
|
-
shell: false,
|
|
57
|
-
});
|
|
58
|
-
return parseToolboxDescription(result.stdout, filePath);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function parseToolboxDescription(stdout, filePath) {
|
|
62
|
-
try {
|
|
63
|
-
const parsed = JSON.parse(stdout);
|
|
64
|
-
return {
|
|
65
|
-
format: 'json',
|
|
66
|
-
description: parsed.description || `Toolbox tool at ${filePath}`,
|
|
67
|
-
input: normalizeToolboxInputSchema(parsed.input ?? parsed.args ?? parsed.inputSchema ?? parsed.schema),
|
|
68
|
-
raw: parsed,
|
|
69
|
-
};
|
|
70
|
-
} catch {
|
|
71
|
-
// Fall through to the documented text format.
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
const lines = stdout.split(/\r?\n/).filter(Boolean);
|
|
75
|
-
const description = lines
|
|
76
|
-
.filter((line) => line.startsWith('description: '))
|
|
77
|
-
.map((line) => line.replace('description: ', '').trim())
|
|
78
|
-
.join('\n');
|
|
79
|
-
const input = lines
|
|
80
|
-
.filter((line) => !line.startsWith('name: ') && !line.startsWith('description: '))
|
|
81
|
-
.map((line) => {
|
|
82
|
-
const match = line.match(/^([^:]+):\s*(.*)$/);
|
|
83
|
-
return match ? parseTextParameter(match[1], match[2]) : undefined;
|
|
84
|
-
})
|
|
85
|
-
.filter(Boolean);
|
|
86
|
-
return {
|
|
87
|
-
format: 'text',
|
|
88
|
-
description: description || `Toolbox tool at ${filePath}`,
|
|
89
|
-
input,
|
|
90
|
-
raw: stdout,
|
|
91
|
-
};
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
function parseTextParameter(name, definition = '') {
|
|
95
|
-
const [first = '', ...rest] = definition.trim().split(/\s+/);
|
|
96
|
-
const knownTypes = new Set(['string', 'number', 'integer', 'boolean', 'array', 'object']);
|
|
97
|
-
if (!first || first.toLowerCase() === 'optional') {
|
|
98
|
-
return { name, type: 'string', description: ['optional', ...rest].filter(Boolean).join(' ') };
|
|
99
|
-
}
|
|
100
|
-
const optionalSuffix = first.endsWith('?');
|
|
101
|
-
const type = optionalSuffix ? first.slice(0, -1) : first;
|
|
102
|
-
if (knownTypes.has(type)) {
|
|
103
|
-
return { name, type, description: optionalText(rest.join(' '), optionalSuffix) };
|
|
104
|
-
}
|
|
105
|
-
return { name, type: 'string', description: definition.trim() };
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
function optionalText(description, optional) {
|
|
109
|
-
if (!optional) return description.replace(/^\(optional\)\s*/i, 'optional ');
|
|
110
|
-
return description.toLowerCase().startsWith('optional') ? description : `optional ${description}`.trim();
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
function normalizeToolboxInputSchema(input = {}) {
|
|
114
|
-
if (Array.isArray(input)) return input;
|
|
115
|
-
if (input?.type === 'object' && input.properties && typeof input.properties === 'object') {
|
|
116
|
-
return Object.entries(input.properties).map(([name, value]) => ({
|
|
117
|
-
name,
|
|
118
|
-
type: schemaTypeName(value),
|
|
119
|
-
description: value?.description || '',
|
|
120
|
-
}));
|
|
121
|
-
}
|
|
122
|
-
return Object.entries(input).map(([name, value]) => ({
|
|
123
|
-
name,
|
|
124
|
-
type: compactArgType(value),
|
|
125
|
-
description: compactArgDescription(value),
|
|
126
|
-
}));
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
function compactArgType(value) {
|
|
130
|
-
if (Array.isArray(value)) return value[0] || 'string';
|
|
131
|
-
return value?.type || 'string';
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
function compactArgDescription(value) {
|
|
135
|
-
if (Array.isArray(value)) return value[1] || '';
|
|
136
|
-
return value?.description || '';
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
function schemaTypeName(value = {}) {
|
|
140
|
-
const type = value.type || 'string';
|
|
141
|
-
if (type === 'array' && value.items?.type) return `array<${value.items.type}>`;
|
|
142
|
-
return type;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
export function printToolboxSchema(tool) {
|
|
146
|
-
console.log(`# ${tool.name} (toolbox: ${tool.path})`);
|
|
147
|
-
console.log('');
|
|
148
|
-
console.log(tool.schema.description);
|
|
149
|
-
if (tool.schema.input.length === 0) return;
|
|
150
|
-
console.log('');
|
|
151
|
-
console.log('# Schema');
|
|
152
|
-
console.log('');
|
|
153
|
-
for (const input of tool.schema.input) {
|
|
154
|
-
const suffix = input.description ? `: ${input.description}` : '';
|
|
155
|
-
console.log(`- ${input.name} (${input.type})${suffix}`);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
export function parseToolUseArgs(args) {
|
|
160
|
-
const result = {
|
|
161
|
-
toolName: '',
|
|
162
|
-
flags: {},
|
|
163
|
-
onlyOutput: false,
|
|
164
|
-
threadId: `T-${randomUUID()}`,
|
|
165
|
-
};
|
|
166
|
-
|
|
167
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
168
|
-
const token = args[index];
|
|
169
|
-
if (token === '--only' && args[index + 1] === 'output') {
|
|
170
|
-
result.onlyOutput = true;
|
|
171
|
-
index += 1;
|
|
172
|
-
continue;
|
|
173
|
-
}
|
|
174
|
-
if (token === '--thread') {
|
|
175
|
-
result.threadId = args[index + 1] ?? result.threadId;
|
|
176
|
-
index += 1;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
if (token.startsWith('--')) {
|
|
180
|
-
const key = token.slice(2);
|
|
181
|
-
const value = args[index + 1] && !args[index + 1].startsWith('--') ? args[index + 1] : true;
|
|
182
|
-
if (value !== true) index += 1;
|
|
183
|
-
result.flags[key] = value;
|
|
184
|
-
continue;
|
|
185
|
-
}
|
|
186
|
-
if (!result.toolName) result.toolName = token;
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return result;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
function formatToolboxInput(tool, flags, stdin) {
|
|
193
|
-
if (stdin.length > 0) return stdin;
|
|
194
|
-
if (Object.keys(flags).length === 0) return '';
|
|
195
|
-
if (tool.schema.format === 'text') {
|
|
196
|
-
return Object.entries(flags).map(([key, value]) => `${key}: ${value}`).join('\n') + '\n';
|
|
197
|
-
}
|
|
198
|
-
return `${JSON.stringify(flags)}\n`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
export function normalizeToolName(name = '') {
|
|
202
|
-
return name.startsWith('tb__') ? name : `tb__${name}`;
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
export function toolboxTemplate(shell, name) {
|
|
206
|
-
if (shell === 'js') return javascriptToolboxTemplate(name);
|
|
207
|
-
return `#!/usr/bin/env ${shell}
|
|
208
|
-
set -euo pipefail
|
|
209
|
-
|
|
210
|
-
if [ "\${TOOLBOX_ACTION:-describe}" = "describe" ]; then
|
|
211
|
-
cat <<'EOF'
|
|
212
|
-
name: ${name}
|
|
213
|
-
description: ${name.replaceAll('_', ' ')} toolbox tool
|
|
214
|
-
input: string optional Input passed to the tool
|
|
215
|
-
EOF
|
|
216
|
-
exit 0
|
|
217
|
-
fi
|
|
218
|
-
|
|
219
|
-
input="$(cat || true)"
|
|
220
|
-
if [ -n "$input" ]; then
|
|
221
|
-
printf '%s\\n' "$input"
|
|
222
|
-
else
|
|
223
|
-
printf '${name} executed\\n'
|
|
224
|
-
fi
|
|
225
|
-
`;
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
function javascriptToolboxTemplate(name) {
|
|
229
|
-
return `#!/usr/bin/env bun
|
|
230
|
-
const action = process.env.TOOLBOX_ACTION ?? 'describe';
|
|
231
|
-
|
|
232
|
-
if (action === 'describe') showDescription();
|
|
233
|
-
else if (action === 'execute') execute();
|
|
234
|
-
else {
|
|
235
|
-
console.error(\`Unknown action: \${action}\`);
|
|
236
|
-
process.exit(1);
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
function showDescription() {
|
|
240
|
-
process.stdout.write([
|
|
241
|
-
'name: ${name}',
|
|
242
|
-
'description: ${name.replaceAll('_', ' ')} toolbox tool',
|
|
243
|
-
'input: string optional Input passed to the tool',
|
|
244
|
-
].join('\\n'));
|
|
245
|
-
process.stdout.write('\\n');
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
async function execute() {
|
|
249
|
-
const input = await new Response(Bun.stdin).text();
|
|
250
|
-
if (input.trim()) {
|
|
251
|
-
process.stdout.write(input.endsWith('\\n') ? input : \`\${input}\\n\`);
|
|
252
|
-
} else {
|
|
253
|
-
process.stdout.write('${name} executed\\n');
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
`;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
export function executeToolboxTool(tool, flags, stdin, threadId) {
|
|
260
|
-
const input = formatToolboxInput(tool, flags, stdin);
|
|
261
|
-
return spawnSync(tool.path, {
|
|
262
|
-
input,
|
|
263
|
-
env: {
|
|
264
|
-
...process.env,
|
|
265
|
-
TOOLBOX_ACTION: 'execute',
|
|
266
|
-
AGENT: 'coven-code',
|
|
267
|
-
COVEN_CODE_THREAD_ID: threadId,
|
|
268
|
-
AGENT_THREAD_ID: threadId,
|
|
269
|
-
},
|
|
270
|
-
encoding: 'utf8',
|
|
271
|
-
shell: false,
|
|
272
|
-
});
|
|
273
|
-
}
|
package/src/util/fs.mjs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
import { existsSync } from 'node:fs';
|
|
2
|
-
|
|
3
|
-
export function emitJson(value) {
|
|
4
|
-
process.stdout.write(`${JSON.stringify(value)}\n`);
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function displayCwd(cwd = process.cwd()) {
|
|
8
|
-
return String(cwd).replace(/^\/private\/var\//, '/var/');
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function addIfExists(files, filePath) {
|
|
12
|
-
if (existsSync(filePath)) files.push(filePath);
|
|
13
|
-
}
|
package/src/util/glob.mjs
DELETED
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
import { existsSync, readdirSync, statSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
|
-
|
|
4
|
-
export function globMatch(pattern, value) {
|
|
5
|
-
const re = new RegExp(`^${String(pattern).split('*').map(escapeRegex).join('.*')}$`);
|
|
6
|
-
return re.test(value);
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
export function escapeRegex(value) {
|
|
10
|
-
return value.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function hasGlob(rawPath) {
|
|
14
|
-
return /[*?]/.test(rawPath);
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function globToRegex(pattern) {
|
|
18
|
-
let source = '';
|
|
19
|
-
for (let index = 0; index < pattern.length; index += 1) {
|
|
20
|
-
const char = pattern[index];
|
|
21
|
-
const next = pattern[index + 1];
|
|
22
|
-
if (char === '*' && next === '*') {
|
|
23
|
-
source += '.*';
|
|
24
|
-
index += 1;
|
|
25
|
-
} else if (char === '*') {
|
|
26
|
-
source += `[^${escapeRegex(path.sep)}]*`;
|
|
27
|
-
} else if (char === '?') {
|
|
28
|
-
source += `[^${escapeRegex(path.sep)}]`;
|
|
29
|
-
} else {
|
|
30
|
-
source += escapeRegex(char);
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
return new RegExp(`^${source}$`);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function walkFiles(dir) {
|
|
37
|
-
if (!existsSync(dir) || !statSync(dir).isDirectory()) return [];
|
|
38
|
-
const files = [];
|
|
39
|
-
for (const entry of readdirSync(dir)) {
|
|
40
|
-
const filePath = path.join(dir, entry);
|
|
41
|
-
const stat = statSync(filePath);
|
|
42
|
-
if (stat.isDirectory()) files.push(...walkFiles(filePath));
|
|
43
|
-
else if (stat.isFile()) files.push(filePath);
|
|
44
|
-
}
|
|
45
|
-
return files;
|
|
46
|
-
}
|
package/src/util/html.mjs
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
export function decodeHtmlText(value = '') {
|
|
2
|
-
return value
|
|
3
|
-
.replace(new RegExp('&' + 'a' + 'mp;', 'g'), '&')
|
|
4
|
-
.replace(/"/g, '"')
|
|
5
|
-
.replace(/'/g, "'")
|
|
6
|
-
.replace(/</g, '<')
|
|
7
|
-
.replace(/>/g, '>');
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function htmlToText(html) {
|
|
11
|
-
return decodeHtmlText(html
|
|
12
|
-
.replace(/<script[\s\S]*?<\/script>/gi, '')
|
|
13
|
-
.replace(/<style[\s\S]*?<\/style>/gi, '')
|
|
14
|
-
.replace(/<\/(?:h[1-6]|p|div|main|article|section|li|tr)>/gi, '\n')
|
|
15
|
-
.replace(/<br\s*\/?>/gi, '\n')
|
|
16
|
-
.replace(/<[^>]+>/g, ' ')
|
|
17
|
-
.replace(/[ \t]+/g, ' ')
|
|
18
|
-
.replace(/\n\s+/g, '\n')
|
|
19
|
-
.replace(/\n{3,}/g, '\n\n')
|
|
20
|
-
.trim());
|
|
21
|
-
}
|
package/src/util/media.mjs
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
export function detectImageMediaType(raw) {
|
|
2
|
-
if (raw.subarray(0, 8).equals(Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]))) return 'image/png';
|
|
3
|
-
if (raw[0] === 0xff && raw[1] === 0xd8 && raw[2] === 0xff) return 'image/jpeg';
|
|
4
|
-
const header = raw.subarray(0, 12).toString('ascii');
|
|
5
|
-
if (header.startsWith('GIF87a') || header.startsWith('GIF89a')) return 'image/gif';
|
|
6
|
-
if (header.startsWith('RIFF') && header.slice(8, 12) === 'WEBP') return 'image/webp';
|
|
7
|
-
return undefined;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export function imageMediaTypeExtension(mediaType) {
|
|
11
|
-
if (mediaType === 'image/jpeg') return 'jpg';
|
|
12
|
-
return mediaType.split('/').at(-1) || 'img';
|
|
13
|
-
}
|
package/src/util/shell.mjs
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
|
|
3
|
-
export function splitShellWords(line) {
|
|
4
|
-
const tokens = [];
|
|
5
|
-
const re = /"([^"\\]*(?:\\.[^"\\]*)*)"|'([^'\\]*(?:\\.[^'\\]*)*)'|(\S+)/g;
|
|
6
|
-
let match;
|
|
7
|
-
while ((match = re.exec(line))) {
|
|
8
|
-
tokens.push((match[1] ?? match[2] ?? match[3]).replace(/\\(["'])/g, '$1'));
|
|
9
|
-
}
|
|
10
|
-
return tokens;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function shellQuote(value) {
|
|
14
|
-
return `'${String(value).replace(/'/g, `'\\''`)}'`;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function readStdin() {
|
|
18
|
-
if (process.stdin.isTTY) return '';
|
|
19
|
-
try {
|
|
20
|
-
return readFileSync(0, 'utf8');
|
|
21
|
-
} catch {
|
|
22
|
-
return '';
|
|
23
|
-
}
|
|
24
|
-
}
|
package/src/util/table.mjs
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
export function printRows(rows) {
|
|
2
|
-
const widths = [];
|
|
3
|
-
for (const row of rows) {
|
|
4
|
-
row.forEach((cell, index) => {
|
|
5
|
-
widths[index] = Math.max(widths[index] ?? 0, String(cell).length);
|
|
6
|
-
});
|
|
7
|
-
}
|
|
8
|
-
for (const row of rows) {
|
|
9
|
-
console.log(row.map((cell, index) => String(cell).padEnd(widths[index])).join(' ').trimEnd());
|
|
10
|
-
}
|
|
11
|
-
}
|