@vicoa/opencode 0.1.0 → 0.1.2
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 +1 -1
- package/dist/index.js +7 -0
- package/dist/plugin/control.d.ts +1 -0
- package/dist/plugin/control.js +7 -1
- package/package.json +2 -1
- package/dist/commands.d.ts +0 -24
- package/dist/commands.js +0 -228
- package/dist/credentials.d.ts +0 -27
- package/dist/credentials.js +0 -56
- package/dist/format-utils.d.ts +0 -10
- package/dist/format-utils.js +0 -335
- package/dist/message-poller.d.ts +0 -16
- package/dist/message-poller.js +0 -45
- package/dist/plugin/file-sync.d.ts +0 -5
- package/dist/plugin/file-sync.js +0 -187
- package/dist/vicoa-client.d.ts +0 -67
- package/dist/vicoa-client.js +0 -259
package/dist/format-utils.js
DELETED
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
const LANGUAGE_MAP = {
|
|
2
|
-
py: 'python',
|
|
3
|
-
js: 'javascript',
|
|
4
|
-
ts: 'typescript',
|
|
5
|
-
jsx: 'jsx',
|
|
6
|
-
tsx: 'tsx',
|
|
7
|
-
java: 'java',
|
|
8
|
-
cpp: 'cpp',
|
|
9
|
-
c: 'c',
|
|
10
|
-
cs: 'csharp',
|
|
11
|
-
rb: 'ruby',
|
|
12
|
-
go: 'go',
|
|
13
|
-
rs: 'rust',
|
|
14
|
-
php: 'php',
|
|
15
|
-
swift: 'swift',
|
|
16
|
-
kt: 'kotlin',
|
|
17
|
-
yaml: 'yaml',
|
|
18
|
-
yml: 'yaml',
|
|
19
|
-
json: 'json',
|
|
20
|
-
xml: 'xml',
|
|
21
|
-
html: 'html',
|
|
22
|
-
css: 'css',
|
|
23
|
-
scss: 'scss',
|
|
24
|
-
sql: 'sql',
|
|
25
|
-
sh: 'bash',
|
|
26
|
-
bash: 'bash',
|
|
27
|
-
md: 'markdown',
|
|
28
|
-
txt: 'text',
|
|
29
|
-
};
|
|
30
|
-
function truncateText(text, maxLength = 100) {
|
|
31
|
-
if (text.length <= maxLength) {
|
|
32
|
-
return text;
|
|
33
|
-
}
|
|
34
|
-
return `${text.slice(0, maxLength)}...`;
|
|
35
|
-
}
|
|
36
|
-
function detectLanguage(filePath) {
|
|
37
|
-
const extension = filePath.includes('.') ? filePath.split('.').pop() ?? '' : '';
|
|
38
|
-
return LANGUAGE_MAP[extension] ?? '';
|
|
39
|
-
}
|
|
40
|
-
function getString(input, keys, fallback = '') {
|
|
41
|
-
for (const key of keys) {
|
|
42
|
-
const value = input[key];
|
|
43
|
-
if (typeof value === 'string') {
|
|
44
|
-
return value;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return fallback;
|
|
48
|
-
}
|
|
49
|
-
function getBoolean(input, keys, fallback = false) {
|
|
50
|
-
for (const key of keys) {
|
|
51
|
-
const value = input[key];
|
|
52
|
-
if (typeof value === 'boolean') {
|
|
53
|
-
return value;
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
return fallback;
|
|
57
|
-
}
|
|
58
|
-
function getArray(input, keys) {
|
|
59
|
-
for (const key of keys) {
|
|
60
|
-
const value = input[key];
|
|
61
|
-
if (Array.isArray(value)) {
|
|
62
|
-
return value;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return [];
|
|
66
|
-
}
|
|
67
|
-
function formatDiffBlock(oldText, newText) {
|
|
68
|
-
const diffLines = ['```diff'];
|
|
69
|
-
if (!oldText && newText) {
|
|
70
|
-
for (const line of newText.split('\n')) {
|
|
71
|
-
diffLines.push(`+ ${line}`);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
else if (oldText && !newText) {
|
|
75
|
-
for (const line of oldText.split('\n')) {
|
|
76
|
-
diffLines.push(`- ${line}`);
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
else {
|
|
80
|
-
const oldLines = oldText.split('\n');
|
|
81
|
-
const newLines = newText.split('\n');
|
|
82
|
-
const commonPrefix = [];
|
|
83
|
-
const commonSuffix = [];
|
|
84
|
-
for (let i = 0; i < Math.min(oldLines.length, newLines.length); i += 1) {
|
|
85
|
-
if (oldLines[i] === newLines[i]) {
|
|
86
|
-
commonPrefix.push(oldLines[i]);
|
|
87
|
-
}
|
|
88
|
-
else {
|
|
89
|
-
break;
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
const oldRemaining = oldLines.slice(commonPrefix.length);
|
|
93
|
-
const newRemaining = newLines.slice(commonPrefix.length);
|
|
94
|
-
if (oldRemaining.length && newRemaining.length) {
|
|
95
|
-
for (let i = 1; i <= Math.min(oldRemaining.length, newRemaining.length); i += 1) {
|
|
96
|
-
if (oldRemaining[oldRemaining.length - i] === newRemaining[newRemaining.length - i]) {
|
|
97
|
-
commonSuffix.unshift(oldRemaining[oldRemaining.length - i]);
|
|
98
|
-
}
|
|
99
|
-
else {
|
|
100
|
-
break;
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const changedOld = commonSuffix.length
|
|
105
|
-
? oldRemaining.slice(0, oldRemaining.length - commonSuffix.length)
|
|
106
|
-
: oldRemaining;
|
|
107
|
-
const changedNew = commonSuffix.length
|
|
108
|
-
? newRemaining.slice(0, newRemaining.length - commonSuffix.length)
|
|
109
|
-
: newRemaining;
|
|
110
|
-
if ((commonPrefix.length || commonSuffix.length) && (changedOld.length || changedNew.length)) {
|
|
111
|
-
const contextBefore = commonPrefix.slice(-2);
|
|
112
|
-
const contextAfter = commonSuffix.slice(0, 2);
|
|
113
|
-
for (const line of contextBefore) {
|
|
114
|
-
diffLines.push(` ${line}`);
|
|
115
|
-
}
|
|
116
|
-
for (const line of changedOld) {
|
|
117
|
-
diffLines.push(`- ${line}`);
|
|
118
|
-
}
|
|
119
|
-
for (const line of changedNew) {
|
|
120
|
-
diffLines.push(`+ ${line}`);
|
|
121
|
-
}
|
|
122
|
-
for (const line of contextAfter) {
|
|
123
|
-
diffLines.push(` ${line}`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
else {
|
|
127
|
-
for (const line of oldLines) {
|
|
128
|
-
diffLines.push(`- ${line}`);
|
|
129
|
-
}
|
|
130
|
-
for (const line of newLines) {
|
|
131
|
-
diffLines.push(`+ ${line}`);
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
diffLines.push('```');
|
|
136
|
-
return diffLines;
|
|
137
|
-
}
|
|
138
|
-
export function formatToolUsage(toolName, inputData) {
|
|
139
|
-
if (toolName.startsWith('mcp__vicoa__')) {
|
|
140
|
-
return `Using tool: ${toolName}`;
|
|
141
|
-
}
|
|
142
|
-
const normalizedTool = toolName.toLowerCase();
|
|
143
|
-
if (normalizedTool === 'write') {
|
|
144
|
-
const filePath = getString(inputData, ['file_path', 'filePath', 'path', 'filename'], 'unknown');
|
|
145
|
-
const content = getString(inputData, ['content', 'text', 'value']);
|
|
146
|
-
const lang = detectLanguage(filePath);
|
|
147
|
-
return [`Using tool: Write - \`${filePath}\``, `\`\`\`${lang}`, content, '```']
|
|
148
|
-
.filter((line) => line.length > 0)
|
|
149
|
-
.join('\n');
|
|
150
|
-
}
|
|
151
|
-
if (normalizedTool === 'read' || normalizedTool === 'notebookread' || normalizedTool === 'notebookedit') {
|
|
152
|
-
const filePath = getString(inputData, ['file_path', 'filePath', 'path', 'notebook_path'], 'unknown');
|
|
153
|
-
return `Using tool: ${toolName} - \`${filePath}\``;
|
|
154
|
-
}
|
|
155
|
-
if (normalizedTool === 'edit') {
|
|
156
|
-
const filePath = getString(inputData, ['file_path', 'filePath', 'path'], 'unknown');
|
|
157
|
-
const oldString = getString(inputData, ['old_string', 'oldString']);
|
|
158
|
-
const newString = getString(inputData, ['new_string', 'newString']);
|
|
159
|
-
const replaceAll = getBoolean(inputData, ['replace_all', 'replaceAll']);
|
|
160
|
-
const diffLines = [`Using tool: **Edit** - \`${filePath}\``];
|
|
161
|
-
if (replaceAll) {
|
|
162
|
-
diffLines.push('*Replacing all occurrences*');
|
|
163
|
-
}
|
|
164
|
-
diffLines.push('');
|
|
165
|
-
diffLines.push(...formatDiffBlock(oldString, newString));
|
|
166
|
-
return diffLines.join('\n');
|
|
167
|
-
}
|
|
168
|
-
if (normalizedTool === 'multiedit') {
|
|
169
|
-
const filePath = getString(inputData, ['file_path', 'filePath', 'path'], 'unknown');
|
|
170
|
-
const edits = getArray(inputData, ['edits']);
|
|
171
|
-
const lines = [
|
|
172
|
-
`Using tool: **MultiEdit** - \`${filePath}\``,
|
|
173
|
-
`*Making ${edits.length} edit${edits.length === 1 ? '' : 's'}:*`,
|
|
174
|
-
'',
|
|
175
|
-
];
|
|
176
|
-
edits.forEach((edit, index) => {
|
|
177
|
-
const editIndex = index + 1;
|
|
178
|
-
const oldString = typeof edit.old_string === 'string' ? edit.old_string : edit.oldString ?? '';
|
|
179
|
-
const newString = typeof edit.new_string === 'string' ? edit.new_string : edit.newString ?? '';
|
|
180
|
-
const replaceAll = Boolean(edit.replace_all ?? edit.replaceAll ?? false);
|
|
181
|
-
lines.push(replaceAll ? `### Edit ${editIndex} *(replacing all occurrences)*` : `### Edit ${editIndex}`);
|
|
182
|
-
lines.push('');
|
|
183
|
-
lines.push(...formatDiffBlock(oldString, newString));
|
|
184
|
-
lines.push('');
|
|
185
|
-
});
|
|
186
|
-
return lines.join('\n');
|
|
187
|
-
}
|
|
188
|
-
if (normalizedTool === 'bash') {
|
|
189
|
-
const command = getString(inputData, ['command', 'cmd']);
|
|
190
|
-
return `Using tool: Bash - \`${command}\``;
|
|
191
|
-
}
|
|
192
|
-
if (normalizedTool === 'grep' || normalizedTool === 'glob') {
|
|
193
|
-
const pattern = getString(inputData, ['pattern', 'query'], 'unknown');
|
|
194
|
-
const path = getString(inputData, ['path', 'directory'], 'current directory');
|
|
195
|
-
return `Using tool: ${toolName} - \`${truncateText(pattern, 50)}\` in ${path}`;
|
|
196
|
-
}
|
|
197
|
-
if (normalizedTool === 'list' || normalizedTool === 'ls') {
|
|
198
|
-
const path = getString(inputData, ['path'], 'unknown');
|
|
199
|
-
return `Using tool: list - \`${path}\``;
|
|
200
|
-
}
|
|
201
|
-
if (normalizedTool === 'patch') {
|
|
202
|
-
const file = getString(inputData, ['file', 'path'], 'unknown');
|
|
203
|
-
return `Using tool: patch - \`${file}\``;
|
|
204
|
-
}
|
|
205
|
-
if (normalizedTool === 'skill') {
|
|
206
|
-
const name = getString(inputData, ['name', 'skill'], 'unknown');
|
|
207
|
-
return `Using tool: skill - \`${name}\``;
|
|
208
|
-
}
|
|
209
|
-
if (normalizedTool === 'question') {
|
|
210
|
-
const text = getString(inputData, ['text', 'question', 'message'], '');
|
|
211
|
-
return text ? `Asking: ${truncateText(text, 100)}` : 'Using tool: question';
|
|
212
|
-
}
|
|
213
|
-
if (normalizedTool === 'lsp') {
|
|
214
|
-
const command = getString(inputData, ['command', 'method'], 'unknown');
|
|
215
|
-
const file = getString(inputData, ['file', 'path'], '');
|
|
216
|
-
return file ? `Using tool: lsp - ${command} on \`${file}\`` : `Using tool: lsp - ${command}`;
|
|
217
|
-
}
|
|
218
|
-
if (normalizedTool === 'todowrite') {
|
|
219
|
-
const todos = getArray(inputData, ['todos']);
|
|
220
|
-
if (!todos.length) {
|
|
221
|
-
return 'Using tool: TodoWrite - clearing todo list';
|
|
222
|
-
}
|
|
223
|
-
const statusSymbol = {
|
|
224
|
-
pending: '○',
|
|
225
|
-
in_progress: '◐',
|
|
226
|
-
completed: '●',
|
|
227
|
-
};
|
|
228
|
-
const lines = ['Using tool: TodoWrite - Todo List', ''];
|
|
229
|
-
for (const todo of todos) {
|
|
230
|
-
const status = typeof todo.status === 'string' ? todo.status : 'pending';
|
|
231
|
-
const content = typeof todo.content === 'string' ? todo.content : '';
|
|
232
|
-
const symbol = statusSymbol[status] ?? '•';
|
|
233
|
-
lines.push(`${symbol} ${truncateText(content, 100)}`);
|
|
234
|
-
}
|
|
235
|
-
return lines.join('\n');
|
|
236
|
-
}
|
|
237
|
-
if (normalizedTool === 'todoread') {
|
|
238
|
-
return 'Using tool: todoread';
|
|
239
|
-
}
|
|
240
|
-
if (normalizedTool === 'task') {
|
|
241
|
-
const description = getString(inputData, ['description'], 'unknown task');
|
|
242
|
-
const subagentType = getString(inputData, ['subagent_type', 'subagentType', 'agent'], 'unknown');
|
|
243
|
-
return `Using tool: Task - ${truncateText(description, 50)} (agent: ${subagentType})`;
|
|
244
|
-
}
|
|
245
|
-
if (normalizedTool === 'webfetch') {
|
|
246
|
-
const url = getString(inputData, ['url'], 'unknown');
|
|
247
|
-
return `Using tool: WebFetch - \`${truncateText(url, 80)}\``;
|
|
248
|
-
}
|
|
249
|
-
if (normalizedTool === 'websearch') {
|
|
250
|
-
const query = getString(inputData, ['query'], 'unknown');
|
|
251
|
-
return `Using tool: WebSearch - ${truncateText(query, 80)}`;
|
|
252
|
-
}
|
|
253
|
-
if (toolName === 'ListMcpResourcesTool') {
|
|
254
|
-
return 'Using tool: List MCP Resources';
|
|
255
|
-
}
|
|
256
|
-
const defaultKeys = ['file', 'path', 'query', 'content', 'message', 'description', 'name'];
|
|
257
|
-
for (const key of defaultKeys) {
|
|
258
|
-
if (typeof inputData[key] === 'string') {
|
|
259
|
-
return `Using tool: ${toolName} - ${truncateText(String(inputData[key]), 50)}`;
|
|
260
|
-
}
|
|
261
|
-
}
|
|
262
|
-
return `Using tool: ${toolName}`;
|
|
263
|
-
}
|
|
264
|
-
// Most tool outputs don't need truncation - show them in full.
|
|
265
|
-
// Only truncate for tools that tend to produce very large/noisy output.
|
|
266
|
-
const TRUNCATE_OUTPUT_TOOLS = new Set(['']); // set to empty for not to filter message now.
|
|
267
|
-
const TRUNCATE_LIMIT = 500;
|
|
268
|
-
export function formatToolResult(output, toolName) {
|
|
269
|
-
try {
|
|
270
|
-
const parsed = JSON.parse(output);
|
|
271
|
-
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
272
|
-
const keys = Object.keys(parsed).slice(0, 3);
|
|
273
|
-
const summary = `JSON object with keys: ${keys.join(', ')}`;
|
|
274
|
-
return keys.length < Object.keys(parsed).length ? `${summary} and ${Object.keys(parsed).length - keys.length} more` : summary;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
catch {
|
|
278
|
-
// not JSON
|
|
279
|
-
}
|
|
280
|
-
// Default: don't truncate tool output unless it's in the truncate list
|
|
281
|
-
if (toolName && TRUNCATE_OUTPUT_TOOLS.has(toolName.toLowerCase())) {
|
|
282
|
-
return truncateText(output, TRUNCATE_LIMIT);
|
|
283
|
-
}
|
|
284
|
-
return output;
|
|
285
|
-
}
|
|
286
|
-
// Tools whose Result line is noise: either raw file/list content (read-side)
|
|
287
|
-
// or a boilerplate success confirmation like "Edit applied successfully."
|
|
288
|
-
// (write-side). Only the usage line is shown for these.
|
|
289
|
-
const SUPPRESS_OUTPUT_TOOLS = new Set([
|
|
290
|
-
'read', 'notebookread', 'list', 'ls', 'glob', 'grep', 'todoread', 'lsp',
|
|
291
|
-
'write', 'edit', 'multiedit', 'patch', 'notebookedit', 'todowrite',
|
|
292
|
-
]);
|
|
293
|
-
export function shouldSuppressToolOutput(toolName) {
|
|
294
|
-
return SUPPRESS_OUTPUT_TOOLS.has(toolName.toLowerCase());
|
|
295
|
-
}
|
|
296
|
-
export function formatToolPart(toolPart) {
|
|
297
|
-
const base = formatToolUsage(toolPart.tool, toolPart.state.input ?? {});
|
|
298
|
-
if (toolPart.state.status === 'completed') {
|
|
299
|
-
if (shouldSuppressToolOutput(toolPart.tool)) {
|
|
300
|
-
return base;
|
|
301
|
-
}
|
|
302
|
-
const result = toolPart.state.output ? formatToolResult(toolPart.state.output, toolPart.tool) : '[empty]';
|
|
303
|
-
return `${base}\nResult: ${result}`;
|
|
304
|
-
}
|
|
305
|
-
if (toolPart.state.status === 'error') {
|
|
306
|
-
const error = toolPart.state.error ? truncateText(toolPart.state.error, 200) : 'Unknown error';
|
|
307
|
-
return `${base}\nResult: Error - ${error}`;
|
|
308
|
-
}
|
|
309
|
-
return base;
|
|
310
|
-
}
|
|
311
|
-
export function formatReasoningPart(part) {
|
|
312
|
-
if (!part.text) {
|
|
313
|
-
return '';
|
|
314
|
-
}
|
|
315
|
-
return `[Thinking: ${truncateText(part.text, 200)}]`;
|
|
316
|
-
}
|
|
317
|
-
export function formatFilePart(part) {
|
|
318
|
-
if (part.mime?.startsWith('image/') && part.url) {
|
|
319
|
-
const altText = part.filename ?? 'image';
|
|
320
|
-
return ``;
|
|
321
|
-
}
|
|
322
|
-
const source = part.source?.text?.value ?? '';
|
|
323
|
-
const path = part.source?.path ?? part.filename ?? part.url ?? 'file';
|
|
324
|
-
if (source) {
|
|
325
|
-
const lang = detectLanguage(path);
|
|
326
|
-
return [`File: \`${path}\``, `\`\`\`${lang}`, source, '```'].join('\n');
|
|
327
|
-
}
|
|
328
|
-
return `File: \`${path}\``;
|
|
329
|
-
}
|
|
330
|
-
export function formatPatchPart(part) {
|
|
331
|
-
if (part.files?.length) {
|
|
332
|
-
return `Patch updated: ${part.files.join(', ')}`;
|
|
333
|
-
}
|
|
334
|
-
return 'Patch updated';
|
|
335
|
-
}
|
package/dist/message-poller.d.ts
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Polls Vicoa backend for user messages and sends them to OpenCode
|
|
3
|
-
*
|
|
4
|
-
* This mimics the Claude wrapper's message queue and polling functionality.
|
|
5
|
-
*/
|
|
6
|
-
import type { VicoaClient } from './vicoa-client.js';
|
|
7
|
-
export declare class MessagePoller {
|
|
8
|
-
private client;
|
|
9
|
-
private interval;
|
|
10
|
-
private pollIntervalMs;
|
|
11
|
-
private onMessage;
|
|
12
|
-
private log;
|
|
13
|
-
constructor(client: VicoaClient, onMessage: (content: string) => Promise<void>, logFunc?: (level: string, msg: string) => void, pollIntervalMs?: number);
|
|
14
|
-
start(): void;
|
|
15
|
-
stop(): void;
|
|
16
|
-
}
|
package/dist/message-poller.js
DELETED
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Polls Vicoa backend for user messages and sends them to OpenCode
|
|
3
|
-
*
|
|
4
|
-
* This mimics the Claude wrapper's message queue and polling functionality.
|
|
5
|
-
*/
|
|
6
|
-
export class MessagePoller {
|
|
7
|
-
client;
|
|
8
|
-
interval = null;
|
|
9
|
-
pollIntervalMs;
|
|
10
|
-
onMessage;
|
|
11
|
-
log;
|
|
12
|
-
constructor(client, onMessage, logFunc, pollIntervalMs = 1000) {
|
|
13
|
-
this.client = client;
|
|
14
|
-
this.onMessage = onMessage;
|
|
15
|
-
this.pollIntervalMs = pollIntervalMs;
|
|
16
|
-
this.log = logFunc || ((level, msg) => console.log(`[${level}] ${msg}`));
|
|
17
|
-
}
|
|
18
|
-
start() {
|
|
19
|
-
if (this.interval) {
|
|
20
|
-
return;
|
|
21
|
-
}
|
|
22
|
-
this.log('info', 'Starting message poller');
|
|
23
|
-
this.interval = setInterval(async () => {
|
|
24
|
-
try {
|
|
25
|
-
const messages = await this.client.getPendingMessages();
|
|
26
|
-
for (const msg of messages) {
|
|
27
|
-
if (msg.sender_type === 'USER' && msg.content) {
|
|
28
|
-
this.log('debug', `Received user message: ${msg.content.substring(0, 100)}...`);
|
|
29
|
-
await this.onMessage(msg.content);
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
}
|
|
33
|
-
catch (error) {
|
|
34
|
-
this.log('warn', `Error polling messages: ${error}`);
|
|
35
|
-
}
|
|
36
|
-
}, this.pollIntervalMs);
|
|
37
|
-
}
|
|
38
|
-
stop() {
|
|
39
|
-
if (this.interval) {
|
|
40
|
-
clearInterval(this.interval);
|
|
41
|
-
this.interval = null;
|
|
42
|
-
this.log('info', 'Stopped message poller');
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
}
|
package/dist/plugin/file-sync.js
DELETED
|
@@ -1,187 +0,0 @@
|
|
|
1
|
-
import * as fs from 'fs/promises';
|
|
2
|
-
import * as path from 'path';
|
|
3
|
-
import { formatProjectPath } from './path-utils.js';
|
|
4
|
-
// Common patterns to always exclude (matching Python version)
|
|
5
|
-
const DEFAULT_EXCLUDE_PATTERNS = [
|
|
6
|
-
'.git/',
|
|
7
|
-
'.pytest_cache/',
|
|
8
|
-
'.dart_tool/',
|
|
9
|
-
'.ruff_cache/',
|
|
10
|
-
'.mypy_cache/',
|
|
11
|
-
'__pycache__/',
|
|
12
|
-
'node_modules/',
|
|
13
|
-
'.venv/',
|
|
14
|
-
'venv/',
|
|
15
|
-
'.tox/',
|
|
16
|
-
'.eggs/',
|
|
17
|
-
'*.egg-info/',
|
|
18
|
-
'dist/',
|
|
19
|
-
'build/',
|
|
20
|
-
'target/', // Rust build output
|
|
21
|
-
'.next/', // Next.js
|
|
22
|
-
'.nuxt/', // Nuxt
|
|
23
|
-
'coverage/',
|
|
24
|
-
'.coverage',
|
|
25
|
-
'.nyc_output/',
|
|
26
|
-
'*.pyc',
|
|
27
|
-
'*.pyo',
|
|
28
|
-
'*.pyd',
|
|
29
|
-
];
|
|
30
|
-
// Extract directory names from exclude patterns
|
|
31
|
-
function getExcludedDirNames() {
|
|
32
|
-
const excluded = new Set();
|
|
33
|
-
for (const pattern of DEFAULT_EXCLUDE_PATTERNS) {
|
|
34
|
-
// Skip file patterns (contain wildcards)
|
|
35
|
-
if (pattern.includes('*')) {
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
// Strip trailing slashes and add to set
|
|
39
|
-
const dirName = pattern.replace(/\/$/, '');
|
|
40
|
-
if (dirName) {
|
|
41
|
-
excluded.add(dirName);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
return excluded;
|
|
45
|
-
}
|
|
46
|
-
// Load .gitignore patterns
|
|
47
|
-
async function loadGitignorePatterns(projectPath) {
|
|
48
|
-
const gitignorePath = path.join(projectPath, '.gitignore');
|
|
49
|
-
try {
|
|
50
|
-
const content = await fs.readFile(gitignorePath, 'utf-8');
|
|
51
|
-
return content
|
|
52
|
-
.split('\n')
|
|
53
|
-
.map(line => line.trim())
|
|
54
|
-
.filter(line => line && !line.startsWith('#'));
|
|
55
|
-
}
|
|
56
|
-
catch {
|
|
57
|
-
return [];
|
|
58
|
-
}
|
|
59
|
-
}
|
|
60
|
-
// Simple gitignore pattern matcher (basic implementation)
|
|
61
|
-
function matchesGitignorePattern(filePath, patterns) {
|
|
62
|
-
for (const pattern of patterns) {
|
|
63
|
-
const normalizedPattern = pattern.trim();
|
|
64
|
-
if (!normalizedPattern)
|
|
65
|
-
continue;
|
|
66
|
-
// Convert to regex for simple matching
|
|
67
|
-
const regexPattern = normalizedPattern
|
|
68
|
-
.replace(/\./g, '\\.')
|
|
69
|
-
.replace(/\*/g, '.*')
|
|
70
|
-
.replace(/\?/g, '.');
|
|
71
|
-
const regex = new RegExp(`^${regexPattern}`);
|
|
72
|
-
// Check both relative path and basename
|
|
73
|
-
if (regex.test(filePath) || regex.test(path.basename(filePath))) {
|
|
74
|
-
return true;
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
return false;
|
|
78
|
-
}
|
|
79
|
-
// Scan project files recursively
|
|
80
|
-
async function scanProjectFiles(projectPath, vicoaClient, maxFiles = 100000) {
|
|
81
|
-
const base = path.resolve(projectPath);
|
|
82
|
-
try {
|
|
83
|
-
const stats = await fs.stat(base);
|
|
84
|
-
if (!stats.isDirectory()) {
|
|
85
|
-
return [];
|
|
86
|
-
}
|
|
87
|
-
}
|
|
88
|
-
catch {
|
|
89
|
-
return [];
|
|
90
|
-
}
|
|
91
|
-
const excludePatterns = [...DEFAULT_EXCLUDE_PATTERNS];
|
|
92
|
-
const gitignorePatterns = await loadGitignorePatterns(base);
|
|
93
|
-
excludePatterns.push(...gitignorePatterns);
|
|
94
|
-
const skipDirs = getExcludedDirNames();
|
|
95
|
-
const files = [];
|
|
96
|
-
const folders = new Set();
|
|
97
|
-
async function walkDirectory(dirPath, relativePath = '') {
|
|
98
|
-
try {
|
|
99
|
-
const entries = await fs.readdir(dirPath, { withFileTypes: true });
|
|
100
|
-
for (const entry of entries) {
|
|
101
|
-
// Skip excluded directories early
|
|
102
|
-
if (entry.isDirectory() && skipDirs.has(entry.name)) {
|
|
103
|
-
continue;
|
|
104
|
-
}
|
|
105
|
-
const entryRelativePath = relativePath ? path.join(relativePath, entry.name) : entry.name;
|
|
106
|
-
const entryFullPath = path.join(dirPath, entry.name);
|
|
107
|
-
if (entry.isDirectory()) {
|
|
108
|
-
await walkDirectory(entryFullPath, entryRelativePath);
|
|
109
|
-
// Add folder to results (with trailing slash)
|
|
110
|
-
const folderPath = entryRelativePath.replace(/\\/g, '/') + '/';
|
|
111
|
-
folders.add(folderPath);
|
|
112
|
-
}
|
|
113
|
-
else if (entry.isFile()) {
|
|
114
|
-
// Check if file should be excluded
|
|
115
|
-
if (matchesGitignorePattern(entryRelativePath, excludePatterns)) {
|
|
116
|
-
continue;
|
|
117
|
-
}
|
|
118
|
-
files.push(entryRelativePath.replace(/\\/g, '/'));
|
|
119
|
-
// Extract parent folders
|
|
120
|
-
const parts = entryRelativePath.split(path.sep);
|
|
121
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
122
|
-
const folderPath = parts.slice(0, i + 1).join('/') + '/';
|
|
123
|
-
folders.add(folderPath);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
// Stop at max_files limit
|
|
127
|
-
if (files.length >= maxFiles) {
|
|
128
|
-
vicoaClient.log('warn', `Large project detected: ${maxFiles}+ files found`);
|
|
129
|
-
vicoaClient.log('warn', `Only syncing first ${maxFiles} files for performance`);
|
|
130
|
-
vicoaClient.log('warn', 'File mentions may be incomplete');
|
|
131
|
-
return;
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
}
|
|
135
|
-
catch (error) {
|
|
136
|
-
vicoaClient.log('warn', `Error scanning directory ${dirPath}: ${error}`);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
await walkDirectory(base);
|
|
140
|
-
// Combine folders and files
|
|
141
|
-
return Array.from(folders).concat(files);
|
|
142
|
-
}
|
|
143
|
-
// Sync project files to Vicoa backend
|
|
144
|
-
export async function syncProjectFiles(vicoaClient, projectPath) {
|
|
145
|
-
try {
|
|
146
|
-
// Use absolute path for file scanning (needed for fs operations)
|
|
147
|
-
const absolutePath = path.resolve(projectPath);
|
|
148
|
-
vicoaClient.log('info', 'Preparing fuzzy file search with @ ...');
|
|
149
|
-
const files = await scanProjectFiles(absolutePath, vicoaClient);
|
|
150
|
-
if (files.length === 0) {
|
|
151
|
-
return; // No files to sync
|
|
152
|
-
}
|
|
153
|
-
// Send tilde path to backend (consistent with Claude wrapper)
|
|
154
|
-
const formattedPath = formatProjectPath(absolutePath);
|
|
155
|
-
await vicoaClient.syncFiles(formattedPath, files);
|
|
156
|
-
vicoaClient.log('info', `Synced ${files.length} files for fuzzy search`);
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
vicoaClient.log('warn', `Failed to sync project files: ${error}`);
|
|
160
|
-
// Silently fail - don't block plugin startup
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
// Backup vicoa client api call here
|
|
164
|
-
/**
|
|
165
|
-
* Sync project files to Vicoa backend for @ mentions
|
|
166
|
-
*/
|
|
167
|
-
// async syncFiles(projectPath: string, files: string[]): Promise<void> {
|
|
168
|
-
// try {
|
|
169
|
-
// const response = await fetch(`${this.config.baseUrl}/api/v1/files/sync`, {
|
|
170
|
-
// method: 'POST',
|
|
171
|
-
// headers: {
|
|
172
|
-
// 'Authorization': `Bearer ${this.config.apiKey}`,
|
|
173
|
-
// 'Content-Type': 'application/json',
|
|
174
|
-
// },
|
|
175
|
-
// body: JSON.stringify({
|
|
176
|
-
// project_path: projectPath,
|
|
177
|
-
// files,
|
|
178
|
-
// }),
|
|
179
|
-
// });
|
|
180
|
-
// if (!response.ok) {
|
|
181
|
-
// const error = await response.text();
|
|
182
|
-
// this.log('warn', `Failed to sync project files: ${response.statusText} - ${error}`);
|
|
183
|
-
// }
|
|
184
|
-
// } catch (error) {
|
|
185
|
-
// this.log('warn', `Error syncing project files: ${error}`);
|
|
186
|
-
// }
|
|
187
|
-
// }
|
package/dist/vicoa-client.d.ts
DELETED
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Vicoa API client for OpenCode plugin
|
|
3
|
-
*
|
|
4
|
-
* This client uses Vicoa's existing REST APIs to communicate with the dashboard.
|
|
5
|
-
* It mimics the Python VicoaClient functionality but runs in TypeScript/Node.js.
|
|
6
|
-
*/
|
|
7
|
-
export interface VicoaClientConfig {
|
|
8
|
-
apiKey: string;
|
|
9
|
-
baseUrl: string;
|
|
10
|
-
agentType: string;
|
|
11
|
-
agentInstanceId: string;
|
|
12
|
-
logFunc?: (level: string, msg: string) => void;
|
|
13
|
-
}
|
|
14
|
-
export interface VicoaMessage {
|
|
15
|
-
id: string;
|
|
16
|
-
content: string;
|
|
17
|
-
sender_type: 'USER' | 'AGENT';
|
|
18
|
-
requires_user_input: boolean;
|
|
19
|
-
created_at: string;
|
|
20
|
-
}
|
|
21
|
-
export declare class VicoaClient {
|
|
22
|
-
private config;
|
|
23
|
-
lastMessageId: string | null;
|
|
24
|
-
log: (level: string, msg: string) => void;
|
|
25
|
-
constructor(config: VicoaClientConfig);
|
|
26
|
-
/**
|
|
27
|
-
* Register agent instance with Vicoa backend
|
|
28
|
-
*/
|
|
29
|
-
registerAgentInstance(project: string, homeDir: string): Promise<{
|
|
30
|
-
agent_instance_id: string;
|
|
31
|
-
}>;
|
|
32
|
-
/**
|
|
33
|
-
* Sync custom slash commands to Vicoa backend
|
|
34
|
-
*/
|
|
35
|
-
syncCommands(agentType: string, commands: Record<string, {
|
|
36
|
-
description: string;
|
|
37
|
-
}>): Promise<void>;
|
|
38
|
-
/**
|
|
39
|
-
* Send agent message to Vicoa dashboard
|
|
40
|
-
*/
|
|
41
|
-
sendMessage(content: string, requiresUserInput?: boolean): Promise<string | null>;
|
|
42
|
-
/**
|
|
43
|
-
* Send user message from terminal to Vicoa dashboard
|
|
44
|
-
* This is used when the user types a message directly in the OpenCode terminal
|
|
45
|
-
*/
|
|
46
|
-
sendUserMessage(content: string): Promise<string | null>;
|
|
47
|
-
/**
|
|
48
|
-
* Poll for pending user messages from Vicoa dashboard
|
|
49
|
-
*/
|
|
50
|
-
getPendingMessages(): Promise<VicoaMessage[]>;
|
|
51
|
-
/**
|
|
52
|
-
* Request user input (equivalent to Claude wrapper's request_user_input)
|
|
53
|
-
*/
|
|
54
|
-
requestUserInput(messageId: string): Promise<void>;
|
|
55
|
-
/**
|
|
56
|
-
* Update agent instance status
|
|
57
|
-
*/
|
|
58
|
-
updateStatus(status: 'ACTIVE' | 'AWAITING_INPUT' | 'PAUSED' | 'STALE' | 'COMPLETED' | 'FAILED' | 'KILLED' | 'DISCONNECTED' | 'DELETED'): Promise<void>;
|
|
59
|
-
/**
|
|
60
|
-
* Update agent instance title/name
|
|
61
|
-
*/
|
|
62
|
-
updateAgentInstanceName(name: string): Promise<void>;
|
|
63
|
-
/**
|
|
64
|
-
* End session
|
|
65
|
-
*/
|
|
66
|
-
endSession(): Promise<void>;
|
|
67
|
-
}
|