@opencoven/coven-code 0.0.4 → 0.0.7

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 (115) hide show
  1. package/README.md +29 -130
  2. package/bin/coven-code +26 -0
  3. package/install.js +117 -0
  4. package/package.json +25 -22
  5. package/bin/coven-code-sdk.mjs +0 -12
  6. package/bin/coven-code.mjs +0 -19
  7. package/docs/CLI.md +0 -256
  8. package/docs/CONFIGURATION.md +0 -107
  9. package/docs/DEMO.md +0 -453
  10. package/docs/DEVELOPMENT.md +0 -104
  11. package/docs/DOGFOOD-PROTOCOL.md +0 -263
  12. package/docs/MCP-SKILLS-PLUGINS.md +0 -127
  13. package/docs/README.md +0 -39
  14. package/docs/RELEASE.md +0 -33
  15. package/docs/SDK.md +0 -107
  16. package/docs/superpowers/plans/2026-05-25-coven-code-panel-tui.md +0 -904
  17. package/docs/superpowers/plans/2026-05-25-coven-code-rebrand.md +0 -670
  18. package/docs/superpowers/specs/2026-05-25-coven-code-panel-tui-design.md +0 -235
  19. package/docs/superpowers/specs/2026-05-26-slash-first-tui-review.md +0 -63
  20. package/src/agent/fixture.mjs +0 -95
  21. package/src/agent/lane.mjs +0 -136
  22. package/src/cli/dispatch.mjs +0 -66
  23. package/src/cli/execute.mjs +0 -452
  24. package/src/cli/help.mjs +0 -58
  25. package/src/cli/interactive-core.mjs +0 -28
  26. package/src/cli/interactive-io.mjs +0 -101
  27. package/src/cli/interactive-slash.mjs +0 -184
  28. package/src/cli/notifications.mjs +0 -13
  29. package/src/cli/parse.mjs +0 -83
  30. package/src/cli/reasoning.mjs +0 -45
  31. package/src/cli/refs.mjs +0 -162
  32. package/src/cli/repl.mjs +0 -60
  33. package/src/cli/slash-commands.mjs +0 -375
  34. package/src/cli/stream-json.mjs +0 -225
  35. package/src/cli/tui-actions.mjs +0 -72
  36. package/src/cli/tui-blessed.mjs +0 -198
  37. package/src/cli/tui-keys.mjs +0 -80
  38. package/src/cli/tui-lane.mjs +0 -73
  39. package/src/cli/tui-render.mjs +0 -169
  40. package/src/cli/tui-submit.mjs +0 -82
  41. package/src/cli/tui.mjs +0 -174
  42. package/src/commands/agents.mjs +0 -53
  43. package/src/commands/config.mjs +0 -27
  44. package/src/commands/ide.mjs +0 -17
  45. package/src/commands/login.mjs +0 -84
  46. package/src/commands/mcp.mjs +0 -176
  47. package/src/commands/permissions-eval.mjs +0 -122
  48. package/src/commands/permissions-rules.mjs +0 -53
  49. package/src/commands/permissions-text.mjs +0 -112
  50. package/src/commands/permissions.mjs +0 -62
  51. package/src/commands/plugins.mjs +0 -86
  52. package/src/commands/review.mjs +0 -74
  53. package/src/commands/skill.mjs +0 -23
  54. package/src/commands/threads.mjs +0 -165
  55. package/src/commands/tools.mjs +0 -77
  56. package/src/commands/update.mjs +0 -31
  57. package/src/commands/usage.mjs +0 -34
  58. package/src/constants.mjs +0 -52
  59. package/src/main.mjs +0 -87
  60. package/src/mcp/discover.mjs +0 -154
  61. package/src/mcp/local.mjs +0 -55
  62. package/src/mcp/parsers.mjs +0 -46
  63. package/src/mcp/permissions.mjs +0 -52
  64. package/src/mcp/probe.mjs +0 -85
  65. package/src/mcp/registry.mjs +0 -96
  66. package/src/mcp/remote-oauth.mjs +0 -55
  67. package/src/mcp/remote-session.mjs +0 -54
  68. package/src/mcp/remote-sse.mjs +0 -82
  69. package/src/mcp/remote.mjs +0 -74
  70. package/src/plugins/api.mjs +0 -187
  71. package/src/plugins/configuration.mjs +0 -124
  72. package/src/plugins/discover.mjs +0 -84
  73. package/src/plugins/helpers.mjs +0 -187
  74. package/src/plugins/subsystems.mjs +0 -198
  75. package/src/plugins/validators.mjs +0 -142
  76. package/src/sdk-execute.mjs +0 -82
  77. package/src/sdk-install.mjs +0 -187
  78. package/src/sdk-settings.mjs +0 -88
  79. package/src/sdk.mjs +0 -163
  80. package/src/settings/load.mjs +0 -134
  81. package/src/settings/paths.mjs +0 -101
  82. package/src/skills/builtin/building-skills/SKILL.md +0 -20
  83. package/src/skills/discover.mjs +0 -95
  84. package/src/threads/store.mjs +0 -176
  85. package/src/tools/builtin/bash.mjs +0 -110
  86. package/src/tools/builtin/create-file.mjs +0 -66
  87. package/src/tools/builtin/edit-file.mjs +0 -76
  88. package/src/tools/builtin/finder.mjs +0 -73
  89. package/src/tools/builtin/glob.mjs +0 -74
  90. package/src/tools/builtin/grep.mjs +0 -82
  91. package/src/tools/builtin/index.mjs +0 -83
  92. package/src/tools/builtin/librarian.mjs +0 -97
  93. package/src/tools/builtin/look-at.mjs +0 -92
  94. package/src/tools/builtin/mcp.mjs +0 -51
  95. package/src/tools/builtin/mermaid.mjs +0 -59
  96. package/src/tools/builtin/oracle.mjs +0 -56
  97. package/src/tools/builtin/painter.mjs +0 -81
  98. package/src/tools/builtin/plugin-tool.mjs +0 -53
  99. package/src/tools/builtin/read-mcp-resource.mjs +0 -63
  100. package/src/tools/builtin/read-web-page.mjs +0 -72
  101. package/src/tools/builtin/read.mjs +0 -59
  102. package/src/tools/builtin/runtime-content.mjs +0 -31
  103. package/src/tools/builtin/runtime-decisions.mjs +0 -115
  104. package/src/tools/builtin/runtime.mjs +0 -85
  105. package/src/tools/builtin/task.mjs +0 -63
  106. package/src/tools/builtin/toolbox-tool.mjs +0 -57
  107. package/src/tools/builtin/undo-edit.mjs +0 -97
  108. package/src/tools/builtin/web-search.mjs +0 -128
  109. package/src/tools/toolbox.mjs +0 -273
  110. package/src/util/fs.mjs +0 -13
  111. package/src/util/glob.mjs +0 -46
  112. package/src/util/html.mjs +0 -21
  113. package/src/util/media.mjs +0 -13
  114. package/src/util/shell.mjs +0 -24
  115. package/src/util/table.mjs +0 -11
@@ -1,95 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
2
- import os from 'node:os';
3
- import path from 'node:path';
4
- import { fileURLToPath } from 'node:url';
5
- import { CONFIG_SUBDIR } from '../constants.mjs';
6
- import { configDir, expandHomePath } from '../settings/paths.mjs';
7
- import { readSettings } from '../settings/load.mjs';
8
- import { UsageError } from '../cli/parse.mjs';
9
-
10
- const BUILTIN_SKILLS_DIR = path.join(path.dirname(fileURLToPath(import.meta.url)), 'builtin');
11
-
12
- export function listSkills(options = {}) {
13
- const seen = new Set();
14
- const skills = [];
15
- for (const root of [...parsedSkillRoots(options.parsed), ...skillSearchRoots(options.cwd)]) {
16
- if (!existsSync(root.dir)) continue;
17
- for (const entry of readdirSync(root.dir)) {
18
- const dir = path.join(root.dir, entry);
19
- const filePath = path.join(dir, 'SKILL.md');
20
- if (!existsSync(filePath) || !statSync(dir).isDirectory()) continue;
21
- const metadata = readSkillMetadata(dir);
22
- const skill = { ...metadata, dir, filePath, source: root.source };
23
- if (options.includeShadowed || !seen.has(skill.name)) skills.push(skill);
24
- seen.add(skill.name);
25
- }
26
- }
27
- return skills;
28
- }
29
-
30
- export function findSkill(name) {
31
- return listSkills().find((skill) => skill.name === name);
32
- }
33
-
34
- export function skillSearchRoots(cwd = process.cwd()) {
35
- const userRoots = [
36
- { source: 'user', dir: path.join(configDir(), 'agents', 'skills') },
37
- { source: 'user', dir: path.join(configDir(), CONFIG_SUBDIR, 'skills') },
38
- ...configuredSkillRoots(readSettings({})),
39
- ];
40
- const projectRoots = projectSkillRoots(cwd, '.agents', 'skills');
41
- if (readSettings({})['covenCode.skills.disableLegacySkillRoots'] === true) return [...userRoots, ...projectRoots];
42
- return [
43
- ...userRoots,
44
- ...projectRoots,
45
- ...projectSkillRoots(cwd, '.claude', 'skills'),
46
- { source: 'user', dir: path.join(os.homedir(), '.claude', 'skills') },
47
- { source: 'built-in', dir: BUILTIN_SKILLS_DIR },
48
- ];
49
- }
50
-
51
- function projectSkillRoots(cwd, ...parts) {
52
- const roots = [];
53
- const home = os.homedir();
54
- let current = path.resolve(cwd);
55
- while (true) {
56
- roots.push({ source: 'project', dir: path.join(current, ...parts) });
57
- if (current === home || current === path.dirname(current)) break;
58
- current = path.dirname(current);
59
- }
60
- return roots;
61
- }
62
-
63
- export function parsedSkillRoots(parsed = {}) {
64
- return splitSkillPath(parsed.skills).map((entry) => ({ source: 'cli', dir: expandHomePath(entry) }));
65
- }
66
-
67
- function configuredSkillRoots(settings) {
68
- const rawPath = settings['covenCode.skills.path'];
69
- return splitSkillPath(rawPath).map((entry) => ({ source: 'user', dir: expandHomePath(entry) }));
70
- }
71
-
72
- function splitSkillPath(rawPath) {
73
- if (typeof rawPath !== 'string' || rawPath.trim() === '') return [];
74
- return rawPath
75
- .split(path.delimiter)
76
- .filter(Boolean);
77
- }
78
-
79
- export function readSkillMetadata(dir) {
80
- const filePath = path.join(dir, 'SKILL.md');
81
- if (!existsSync(filePath)) throw new UsageError(`Skill at ${dir} is missing SKILL.md`);
82
- const text = readFileSync(filePath, 'utf8');
83
- const frontmatter = text.match(/^---\n([\s\S]*?)\n---/);
84
- const metadata = {};
85
- if (frontmatter) {
86
- for (const line of frontmatter[1].split(/\r?\n/)) {
87
- const match = line.match(/^([A-Za-z0-9_.-]+):\s*(.*)$/);
88
- if (match) metadata[match[1]] = match[2].replace(/^["']|["']$/g, '');
89
- }
90
- }
91
- return {
92
- name: metadata.name || path.basename(dir),
93
- description: metadata.description || '',
94
- };
95
- }
@@ -1,176 +0,0 @@
1
- import { existsSync, readFileSync, readdirSync } from 'node:fs';
2
- import path from 'node:path';
3
- import { CONFIG_SUBDIR } from '../constants.mjs';
4
- import { configDir } from '../settings/paths.mjs';
5
- import { readEffectiveSettings, readSettingsFile, writeSettingsFile } from '../settings/load.mjs';
6
- import { displayCwd } from '../util/fs.mjs';
7
- import { UsageError } from '../cli/parse.mjs';
8
-
9
- export const THREAD_VISIBILITIES = ['private', 'public', 'workspace', 'group', 'unlisted'];
10
- export const THREAD_VISIBILITY_INPUTS = ['private', 'public', 'workspace', 'workspace-shared', 'group', 'group-shared', 'unlisted'];
11
-
12
- export function threadsDir() {
13
- return path.join(configDir(), CONFIG_SUBDIR, 'threads');
14
- }
15
-
16
- export function threadFile(id) {
17
- return path.join(threadsDir(), `${id}.json`);
18
- }
19
-
20
- export function readThread(id) {
21
- const filePath = threadFile(id);
22
- if (!existsSync(filePath)) return undefined;
23
- return readSettingsFile(filePath);
24
- }
25
-
26
- export function listThreads() {
27
- const dir = threadsDir();
28
- if (!existsSync(dir)) return [];
29
- return readdirSync(dir)
30
- .filter((entry) => entry.endsWith('.json'))
31
- .map((entry) => readSettingsFile(path.join(dir, entry)))
32
- .filter((thread) => thread.id)
33
- .sort((a, b) => String(b.createdAt).localeCompare(String(a.createdAt)));
34
- }
35
-
36
- export function latestActiveThread() {
37
- return listThreads()
38
- .filter((thread) => !thread.archived)
39
- .sort((a, b) => String(b.updatedAt).localeCompare(String(a.updatedAt)))[0];
40
- }
41
-
42
- export async function writeThread(thread) {
43
- await writeSettingsFile(threadFile(thread.id), thread);
44
- }
45
-
46
- export function requireThread(id) {
47
- if (!id) throw new UsageError('threads command requires a thread id');
48
- const thread = readThread(id);
49
- if (!thread) throw new UsageError(`Unknown thread: ${id}`);
50
- return thread;
51
- }
52
-
53
- export function threadSearchText(thread) {
54
- return [
55
- thread.id,
56
- thread.title,
57
- thread.cwd,
58
- thread.createdAt,
59
- thread.updatedAt,
60
- ...(thread.labels ?? []),
61
- ...(thread.messages ?? []).map((message) => message.content),
62
- ].join(' ');
63
- }
64
-
65
- export function defaultThreadVisibility(parsed = {}) {
66
- const configured = readEffectiveSettings(parsed)['covenCode.defaultVisibility'];
67
- const originKey = currentRepositoryOriginKey();
68
- const visibility = typeof configured === 'string'
69
- ? configured
70
- : configured?.[originKey] ?? configured?.default;
71
- return normalizeThreadVisibility(parsed.visibility) ?? normalizeThreadVisibility(visibility) ?? 'private';
72
- }
73
-
74
- export function normalizeThreadVisibility(visibility) {
75
- if (visibility === 'workspace-shared') return 'workspace';
76
- if (visibility === 'group-shared') return 'group';
77
- return THREAD_VISIBILITIES.includes(visibility) ? visibility : undefined;
78
- }
79
-
80
- export async function saveThread(id, prompt, result, mode, parsed = {}) {
81
- return saveThreadMessages(id, [
82
- { role: 'user', content: prompt },
83
- { role: 'assistant', content: result },
84
- ], mode, parsed);
85
- }
86
-
87
- export async function persistThreadTurn(id, prompt, result, mode, thread, parsed = {}) {
88
- return persistThreadMessages(id, [
89
- { role: 'user', content: prompt },
90
- { role: 'assistant', content: result },
91
- ], mode, thread, parsed);
92
- }
93
-
94
- export async function saveThreadMessages(id, messages, mode, parsed = {}) {
95
- const now = new Date().toISOString();
96
- const firstUser = messages.find((message) => message.role === 'user')?.content ?? '';
97
- const existing = readThread(id);
98
- const thread = {
99
- id,
100
- title: existing?.title === '(pending thread)' || !existing?.title
101
- ? firstUser.split(/\r?\n/).find(Boolean)?.slice(0, 120) || '(empty prompt)'
102
- : existing.title,
103
- cwd: displayCwd(),
104
- mode,
105
- visibility: defaultThreadVisibility(parsed),
106
- labels: normalizedLabels(parsed.labels),
107
- archived: Boolean(parsed.archive),
108
- createdAt: existing?.createdAt ?? now,
109
- updatedAt: now,
110
- messages: [...(existing?.messages ?? []), ...messages],
111
- };
112
- await writeThread(thread);
113
- return thread;
114
- }
115
-
116
- export async function persistThreadMessages(id, messages, mode, thread, parsed = {}) {
117
- if (!thread) {
118
- return saveThreadMessages(id, messages, mode, parsed);
119
- }
120
- thread.mode = mode;
121
- thread.visibility = normalizeThreadVisibility(parsed.visibility) ?? thread.visibility;
122
- thread.labels = mergeLabels(thread.labels, parsed.labels);
123
- if (parsed.archive) thread.archived = true;
124
- thread.updatedAt = new Date().toISOString();
125
- thread.messages.push(...messages);
126
- await writeThread(thread);
127
- return thread;
128
- }
129
-
130
- export function threadContinuationPrompt(thread, prompt) {
131
- return `[thread:${thread.id}]\n${thread.messages.map((message) => `${message.role}: ${message.content}`).join('\n')}\n[/thread]\n${prompt}`;
132
- }
133
-
134
- function normalizedLabels(labels = []) {
135
- return [...new Set(labels.map((label) => String(label).trim()).filter(Boolean))];
136
- }
137
-
138
- function mergeLabels(existing = [], labels = []) {
139
- return [...new Set([...existing, ...normalizedLabels(labels)])];
140
- }
141
-
142
- function currentRepositoryOriginKey() {
143
- const configPath = findGitConfig(process.cwd());
144
- if (!configPath) return undefined;
145
- try {
146
- const config = readFileSync(configPath, 'utf8');
147
- const originUrl = config.match(/\[remote "origin"\][\s\S]*?\n\s*url\s*=\s*([^\r\n]+)/)?.[1]?.trim();
148
- return normalizeGitOrigin(originUrl);
149
- } catch {
150
- return undefined;
151
- }
152
- }
153
-
154
- function findGitConfig(cwd) {
155
- let current = path.resolve(cwd);
156
- while (true) {
157
- const candidate = path.join(current, '.git', 'config');
158
- if (existsSync(candidate)) return candidate;
159
- const parent = path.dirname(current);
160
- if (parent === current || current === path.resolve(configDir(), '..')) return undefined;
161
- current = parent;
162
- }
163
- }
164
-
165
- function normalizeGitOrigin(originUrl = '') {
166
- const text = originUrl.replace(/\.git$/, '');
167
- const scp = text.match(/^git@([^:]+):(.+)$/);
168
- if (scp) return `${scp[1]}/${scp[2]}`;
169
- try {
170
- const url = new URL(text);
171
- const pathName = url.pathname.replace(/^\/+/, '');
172
- return pathName ? `${url.hostname}/${pathName}` : undefined;
173
- } catch {
174
- return undefined;
175
- }
176
- }
@@ -1,110 +0,0 @@
1
- import { spawnSync } from 'node:child_process';
2
- import { THREAD_URL_BASE } from '../../constants.mjs';
3
- import { resolvePermissionDecision } from '../../commands/permissions.mjs';
4
- import { runPluginEventHandlers } from '../../plugins/discover.mjs';
5
- import { readEffectiveSettings } from '../../settings/load.mjs';
6
- import { shellQuote, splitShellWords } from '../../util/shell.mjs';
7
- import { isToolDisabled } from '../toolbox.mjs';
8
- import {
9
- applyToolCallDecision,
10
- createToolUseID,
11
- permissionDeniedOutput,
12
- pluginResultOutput,
13
- pluginToolCallEvent,
14
- pluginToolResultDecisionExitCode,
15
- pluginToolResultEvent,
16
- pluginToolUseBlock,
17
- toolCallDecisionToolRun,
18
- validateToolCallDecision,
19
- } from './runtime.mjs';
20
-
21
- export const TOOL_NAME = 'Bash';
22
-
23
- export async function executePromptBashToolRequest(request, stdin = '', parsed = {}, plugins = { handlers: {} }, threadId = '') {
24
- if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
25
- request = { ...request, flags: normalizeBashInput(request.flags) };
26
- if (!request.flags.command) return { output: 'Bash requires --command' };
27
- const toolUseID = createToolUseID();
28
- const callDecision = await runPluginEventHandlers(
29
- plugins.handlers['tool.call'],
30
- pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
31
- validateToolCallDecision,
32
- );
33
- const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
34
- if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
35
- request = { ...callResult.request, flags: normalizeBashInput(callResult.request.flags) };
36
- const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
37
- if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
38
- return {
39
- output: permissionDeniedOutput(TOOL_NAME, decision),
40
- permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
41
- };
42
- }
43
- const result = spawnSync(commandForBashExecution(request.flags.command, parsed, threadId), {
44
- cwd: process.cwd(),
45
- input: stdin,
46
- shell: true,
47
- encoding: 'utf8',
48
- env: {
49
- ...process.env,
50
- COVEN_CODE_THREAD_ID: threadId,
51
- AGENT_THREAD_ID: threadId,
52
- },
53
- });
54
- if (result.stderr) process.stderr.write(result.stderr);
55
- process.exitCode = result.status ?? 0;
56
- const output = (result.stdout ?? '').trimEnd();
57
- const resultDecision = await runPluginEventHandlers(
58
- plugins.handlers['tool.result'],
59
- pluginToolResultEvent(TOOL_NAME, request.flags, (result.status ?? 0) === 0 ? 'done' : 'error', output, threadId, toolUseID),
60
- );
61
- const exitCode = pluginToolResultDecisionExitCode(resultDecision, result.status ?? 0);
62
- return {
63
- output: pluginResultOutput(resultDecision, output),
64
- exitCode,
65
- toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
66
- };
67
- }
68
-
69
- function normalizeBashInput(input = {}) {
70
- const command = String(input.command ?? input.cmd ?? '');
71
- return { ...input, command, cmd: command };
72
- }
73
-
74
- function commandForBashExecution(command, parsed = {}, threadId = '') {
75
- if (!isGitCommitCommand(command)) return command;
76
- const trailers = gitCommitTrailers(parsed, threadId);
77
- if (!trailers.length) return command;
78
- return `${command} ${trailers.map((trailer) => `--trailer ${shellQuote(trailer)}`).join(' ')}`;
79
- }
80
-
81
- function isGitCommitCommand(command) {
82
- const words = splitShellWords(command);
83
- if (words[0] !== 'git') return false;
84
- return gitSubcommand(words) === 'commit';
85
- }
86
-
87
- function gitSubcommand(words) {
88
- for (let index = 1; index < words.length; index += 1) {
89
- const word = words[index];
90
- if (word === '-C' || word === '-c' || word === '--git-dir' || word === '--work-tree' || word === '--namespace') {
91
- index += 1;
92
- continue;
93
- }
94
- if (word.startsWith('-')) continue;
95
- return word;
96
- }
97
- return '';
98
- }
99
-
100
- function gitCommitTrailers(parsed, threadId) {
101
- const settings = readEffectiveSettings(parsed);
102
- const trailers = [];
103
- if (settings['covenCode.git.commit.thread.enabled'] !== false && threadId) {
104
- trailers.push(`Coven-Code-Thread: ${THREAD_URL_BASE}/${threadId}`);
105
- }
106
- if (settings['covenCode.git.commit.coauthor.enabled'] === true) {
107
- trailers.push('Co-authored-by: Coven Code <coven-code@opencoven.local>');
108
- }
109
- return trailers;
110
- }
@@ -1,66 +0,0 @@
1
- import { mkdirSync, writeFileSync } 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 { 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 const TOOL_NAME = 'create_file';
19
-
20
- export async function executePromptCreateFileToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
21
- if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
22
- request = { ...request, flags: normalizeCreateFileInput(request.flags) };
23
- if (!request.flags.path) return { output: 'create_file requires --path' };
24
- const toolUseID = createToolUseID();
25
- const callDecision = await runPluginEventHandlers(
26
- plugins.handlers['tool.call'],
27
- pluginToolCallEvent(TOOL_NAME, request.flags, threadId, toolUseID),
28
- validateToolCallDecision,
29
- );
30
- const callResult = applyToolCallDecision(TOOL_NAME, request, callDecision);
31
- if (callResult.output) return toolCallDecisionToolRun(TOOL_NAME, request.flags, toolUseID, callResult);
32
- request = { ...callResult.request, flags: normalizeCreateFileInput(callResult.request.flags) };
33
- const decision = resolvePermissionDecision(TOOL_NAME, request.flags, parsed, { threadId });
34
- if (!parsed.dangerouslyAllowAll && decision.action !== 'allow') {
35
- return {
36
- output: permissionDeniedOutput(TOOL_NAME, decision),
37
- permissionDenials: [{ tool: TOOL_NAME, action: decision.action, reason: 'permission' }],
38
- };
39
- }
40
- const output = createBuiltinFile(request.flags.path, request.flags.content);
41
- const resultDecision = await runPluginEventHandlers(
42
- plugins.handlers['tool.result'],
43
- pluginToolResultEvent(TOOL_NAME, request.flags, 'done', output, threadId, toolUseID),
44
- );
45
- return {
46
- ...pluginTextToolRunResult(resultDecision, output),
47
- toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
48
- };
49
- }
50
-
51
- function normalizeCreateFileInput(input = {}) {
52
- const filePath = input.path ?? input.file ?? input.file_path;
53
- const content = input.content ?? input.text ?? input.body ?? '';
54
- return {
55
- ...input,
56
- path: filePath ? String(filePath) : '',
57
- content: String(content ?? ''),
58
- };
59
- }
60
-
61
- function createBuiltinFile(filePath, content) {
62
- const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
63
- mkdirSync(path.dirname(absolutePath), { recursive: true });
64
- writeFileSync(absolutePath, content, { encoding: 'utf8', flag: 'wx' });
65
- return `Created ${filePath}`;
66
- }
@@ -1,76 +0,0 @@
1
- import { readFileSync, writeFileSync } 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 { 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
- import { recordEditUndo } from './undo-edit.mjs';
18
-
19
- export const TOOL_NAME = 'edit_file';
20
-
21
- export async function executePromptEditFileToolRequest(request, parsed = {}, plugins = { handlers: {} }, threadId = '') {
22
- if (isToolDisabled(TOOL_NAME, 'built-in', parsed)) return { output: `Tool disabled: ${TOOL_NAME}` };
23
- request = { ...request, flags: normalizeEditFileInput(request.flags) };
24
- if (!request.flags.path) return { output: 'edit_file requires --path' };
25
- if (!request.flags.old_string) return { output: 'edit_file requires --old' };
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: normalizeEditFileInput(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 editResult = editBuiltinFile(request.flags.path, request.flags.old_string, request.flags.new_string, threadId);
43
- const resultDecision = await runPluginEventHandlers(
44
- plugins.handlers['tool.result'],
45
- pluginToolResultEvent(TOOL_NAME, request.flags, editResult.status, editResult.output, threadId, toolUseID),
46
- );
47
- return {
48
- ...pluginTextToolRunResult(resultDecision, editResult.output, editResult.status === 'done' ? 0 : 1),
49
- toolUse: pluginToolUseBlock(TOOL_NAME, request.flags, toolUseID),
50
- };
51
- }
52
-
53
- function normalizeEditFileInput(input = {}) {
54
- const filePath = input.path ?? input.file ?? input.file_path;
55
- const oldString = input.old_string ?? input.old ?? input.search ?? input.find;
56
- const newString = input.new_string ?? input.new ?? input.replacement ?? input.replace ?? '';
57
- return {
58
- ...input,
59
- path: filePath ? String(filePath) : '',
60
- old: oldString ? String(oldString) : '',
61
- new: String(newString ?? ''),
62
- old_string: oldString ? String(oldString) : '',
63
- new_string: String(newString ?? ''),
64
- };
65
- }
66
-
67
- function editBuiltinFile(filePath, oldString, newString, threadId = '') {
68
- const absolutePath = path.isAbsolute(filePath) ? filePath : path.resolve(process.cwd(), filePath);
69
- const current = readFileSync(absolutePath, 'utf8');
70
- if (!current.includes(oldString)) {
71
- return { status: 'error', output: `No match found in ${filePath}` };
72
- }
73
- recordEditUndo(threadId, { path: filePath, absolutePath, content: current });
74
- writeFileSync(absolutePath, current.replace(oldString, newString), 'utf8');
75
- return { status: 'done', output: `Edited ${filePath}` };
76
- }
@@ -1,73 +0,0 @@
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
- }
@@ -1,74 +0,0 @@
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
- }