tuna-agent 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/dist/agents/claude-code-adapter.d.ts +3 -1
- package/dist/agents/claude-code-adapter.js +28 -4
- package/dist/agents/factory.d.ts +2 -1
- package/dist/agents/factory.js +2 -2
- package/dist/browser/actions/download.d.ts +16 -0
- package/dist/browser/actions/download.js +39 -0
- package/dist/browser/actions/emulation.d.ts +53 -0
- package/dist/browser/actions/emulation.js +103 -0
- package/dist/browser/actions/evaluate.d.ts +29 -0
- package/dist/browser/actions/evaluate.js +92 -0
- package/dist/browser/actions/interaction.d.ts +79 -0
- package/dist/browser/actions/interaction.js +210 -0
- package/dist/browser/actions/keyboard.d.ts +6 -0
- package/dist/browser/actions/keyboard.js +9 -0
- package/dist/browser/actions/navigation.d.ts +40 -0
- package/dist/browser/actions/navigation.js +92 -0
- package/dist/browser/actions/wait.d.ts +12 -0
- package/dist/browser/actions/wait.js +33 -0
- package/dist/browser/browser.d.ts +722 -0
- package/dist/browser/browser.js +1066 -0
- package/dist/browser/capture/activity.d.ts +22 -0
- package/dist/browser/capture/activity.js +39 -0
- package/dist/browser/capture/pdf.d.ts +6 -0
- package/dist/browser/capture/pdf.js +6 -0
- package/dist/browser/capture/response.d.ts +8 -0
- package/dist/browser/capture/response.js +28 -0
- package/dist/browser/capture/screenshot.d.ts +30 -0
- package/dist/browser/capture/screenshot.js +72 -0
- package/dist/browser/capture/trace.d.ts +13 -0
- package/dist/browser/capture/trace.js +19 -0
- package/dist/browser/chrome-launcher.d.ts +8 -0
- package/dist/browser/chrome-launcher.js +543 -0
- package/dist/browser/connection.d.ts +42 -0
- package/dist/browser/connection.js +359 -0
- package/dist/browser/index.d.ts +6 -0
- package/dist/browser/index.js +3 -0
- package/dist/browser/security.d.ts +51 -0
- package/dist/browser/security.js +357 -0
- package/dist/browser/snapshot/ai-snapshot.d.ts +12 -0
- package/dist/browser/snapshot/ai-snapshot.js +47 -0
- package/dist/browser/snapshot/aria-snapshot.d.ts +26 -0
- package/dist/browser/snapshot/aria-snapshot.js +121 -0
- package/dist/browser/snapshot/ref-map.d.ts +31 -0
- package/dist/browser/snapshot/ref-map.js +250 -0
- package/dist/browser/storage/index.d.ts +36 -0
- package/dist/browser/storage/index.js +65 -0
- package/dist/browser/types.d.ts +429 -0
- package/dist/browser/types.js +2 -0
- package/dist/cli/commands/extension.d.ts +10 -0
- package/dist/cli/commands/extension.js +86 -0
- package/dist/cli/index.js +12 -0
- package/dist/daemon/extension-handlers.d.ts +63 -0
- package/dist/daemon/extension-handlers.js +630 -0
- package/dist/daemon/index.js +173 -44
- package/dist/daemon/ws-client.d.ts +28 -8
- package/dist/daemon/ws-client.js +68 -62
- package/dist/mcp/browser-server.d.ts +11 -0
- package/dist/mcp/browser-server.js +467 -0
- package/dist/mcp/knowledge-server.d.ts +11 -0
- package/dist/mcp/knowledge-server.js +263 -0
- package/dist/mcp/setup.d.ts +20 -0
- package/dist/mcp/setup.js +94 -0
- package/dist/types/index.d.ts +2 -0
- package/dist/utils/claude-cli.d.ts +2 -0
- package/dist/utils/claude-cli.js +29 -9
- package/dist/utils/message-schemas.d.ts +4 -1
- package/dist/utils/message-schemas.js +6 -1
- package/package.json +2 -1
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Knowledge MCP Server for Tuna Agent
|
|
4
|
+
*
|
|
5
|
+
* Stdio-based MCP server that exposes knowledge tools to Claude Code.
|
|
6
|
+
* Communicates with the Tuna API using agent token auth.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* node knowledge-server.js --api-url https://api.tuna.ai --token xxx --agent-id abc123
|
|
10
|
+
*/
|
|
11
|
+
function parseArgs() {
|
|
12
|
+
const args = process.argv.slice(2);
|
|
13
|
+
let apiUrl = '';
|
|
14
|
+
let token = '';
|
|
15
|
+
let agentId = '';
|
|
16
|
+
for (let i = 0; i < args.length; i++) {
|
|
17
|
+
if (args[i] === '--api-url' && args[i + 1])
|
|
18
|
+
apiUrl = args[++i];
|
|
19
|
+
else if (args[i] === '--token' && args[i + 1])
|
|
20
|
+
token = args[++i];
|
|
21
|
+
else if (args[i] === '--agent-id' && args[i + 1])
|
|
22
|
+
agentId = args[++i];
|
|
23
|
+
}
|
|
24
|
+
if (!apiUrl || !token || !agentId) {
|
|
25
|
+
process.stderr.write('Usage: knowledge-server --api-url URL --token TOKEN --agent-id ID\n');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
return { apiUrl, token, agentId };
|
|
29
|
+
}
|
|
30
|
+
// ===== API Client =====
|
|
31
|
+
async function apiCall(config, method, path, body) {
|
|
32
|
+
const url = `${config.apiUrl}${path}`;
|
|
33
|
+
const headers = {
|
|
34
|
+
'Authorization': `Bearer ${config.token}`,
|
|
35
|
+
'Content-Type': 'application/json',
|
|
36
|
+
};
|
|
37
|
+
const options = { method, headers };
|
|
38
|
+
if (body)
|
|
39
|
+
options.body = JSON.stringify(body);
|
|
40
|
+
const res = await fetch(url, options);
|
|
41
|
+
const json = await res.json();
|
|
42
|
+
if (!res.ok || (json.code && json.code >= 400)) {
|
|
43
|
+
throw new Error(json.message || `API error: ${res.status}`);
|
|
44
|
+
}
|
|
45
|
+
return json.data;
|
|
46
|
+
}
|
|
47
|
+
function sendResponse(res) {
|
|
48
|
+
// Claude Code uses newline-delimited JSON (not Content-Length framing)
|
|
49
|
+
process.stdout.write(JSON.stringify(res) + '\n');
|
|
50
|
+
}
|
|
51
|
+
function sendResult(id, result) {
|
|
52
|
+
sendResponse({ jsonrpc: '2.0', id: id ?? null, result });
|
|
53
|
+
}
|
|
54
|
+
function sendError(id, code, message) {
|
|
55
|
+
sendResponse({ jsonrpc: '2.0', id: id ?? null, error: { code, message } });
|
|
56
|
+
}
|
|
57
|
+
// ===== Tool Definitions =====
|
|
58
|
+
const TOOLS = [
|
|
59
|
+
{
|
|
60
|
+
name: 'list_knowledge',
|
|
61
|
+
description: 'List knowledge items accessible to this agent. Can list root items or items inside a specific folder.',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
parent_id: { type: 'string', description: 'Folder ID to list children of. Omit to list root-level items.' },
|
|
66
|
+
},
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
{
|
|
70
|
+
name: 'read_knowledge',
|
|
71
|
+
description: 'Read the full content of a knowledge document by its ID. Returns the markdown content.',
|
|
72
|
+
inputSchema: {
|
|
73
|
+
type: 'object',
|
|
74
|
+
properties: {
|
|
75
|
+
knowledge_id: { type: 'string', description: 'The ID of the knowledge document to read' },
|
|
76
|
+
},
|
|
77
|
+
required: ['knowledge_id'],
|
|
78
|
+
},
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
name: 'create_knowledge',
|
|
82
|
+
description: 'Create a new knowledge document or folder. Content should be in markdown format for documents.',
|
|
83
|
+
inputSchema: {
|
|
84
|
+
type: 'object',
|
|
85
|
+
properties: {
|
|
86
|
+
name: { type: 'string', description: 'Name/title of the knowledge item' },
|
|
87
|
+
content: { type: 'string', description: 'Markdown content (required for documents, omit for folders)' },
|
|
88
|
+
description: { type: 'string', description: 'Short description of what this item contains' },
|
|
89
|
+
kind: { type: 'string', enum: ['document', 'folder'], description: 'Type of knowledge item. Default: document' },
|
|
90
|
+
parent_id: { type: 'string', description: 'Parent folder ID to create inside. Omit for root level.' },
|
|
91
|
+
},
|
|
92
|
+
required: ['name'],
|
|
93
|
+
},
|
|
94
|
+
},
|
|
95
|
+
{
|
|
96
|
+
name: 'update_knowledge',
|
|
97
|
+
description: 'Update an existing knowledge document. Can update name, description, and/or content.',
|
|
98
|
+
inputSchema: {
|
|
99
|
+
type: 'object',
|
|
100
|
+
properties: {
|
|
101
|
+
knowledge_id: { type: 'string', description: 'The ID of the knowledge document to update' },
|
|
102
|
+
name: { type: 'string', description: 'New name (optional)' },
|
|
103
|
+
content: { type: 'string', description: 'New markdown content (optional)' },
|
|
104
|
+
description: { type: 'string', description: 'New description (optional)' },
|
|
105
|
+
},
|
|
106
|
+
required: ['knowledge_id'],
|
|
107
|
+
},
|
|
108
|
+
},
|
|
109
|
+
];
|
|
110
|
+
// ===== Request Handler =====
|
|
111
|
+
async function handleRequest(config, req) {
|
|
112
|
+
try {
|
|
113
|
+
switch (req.method) {
|
|
114
|
+
case 'initialize':
|
|
115
|
+
sendResult(req.id, {
|
|
116
|
+
protocolVersion: '2024-11-05',
|
|
117
|
+
capabilities: { tools: {} },
|
|
118
|
+
serverInfo: { name: 'tuna-knowledge', version: '1.0.0' },
|
|
119
|
+
});
|
|
120
|
+
break;
|
|
121
|
+
case 'notifications/initialized':
|
|
122
|
+
// No response needed for notifications
|
|
123
|
+
break;
|
|
124
|
+
case 'tools/list':
|
|
125
|
+
sendResult(req.id, { tools: TOOLS });
|
|
126
|
+
break;
|
|
127
|
+
case 'tools/call': {
|
|
128
|
+
const toolName = req.params?.name ?? '';
|
|
129
|
+
const args = req.params?.arguments ?? {};
|
|
130
|
+
const result = await handleToolCall(config, toolName, args);
|
|
131
|
+
sendResult(req.id, result);
|
|
132
|
+
break;
|
|
133
|
+
}
|
|
134
|
+
case 'ping':
|
|
135
|
+
sendResult(req.id, {});
|
|
136
|
+
break;
|
|
137
|
+
default:
|
|
138
|
+
if (req.id !== undefined) {
|
|
139
|
+
sendError(req.id, -32601, `Method not found: ${req.method}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
catch (err) {
|
|
144
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
145
|
+
if (req.id !== undefined) {
|
|
146
|
+
sendError(req.id, -32603, message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async function handleToolCall(config, toolName, args) {
|
|
151
|
+
try {
|
|
152
|
+
switch (toolName) {
|
|
153
|
+
case 'list_knowledge': {
|
|
154
|
+
let url = `/agent-knowledge?agent_id=${config.agentId}`;
|
|
155
|
+
if (args.parent_id)
|
|
156
|
+
url += `&parent_id=${args.parent_id}`;
|
|
157
|
+
const data = await apiCall(config, 'GET', url);
|
|
158
|
+
const items = data.items || [];
|
|
159
|
+
if (items.length === 0) {
|
|
160
|
+
return { content: [{ type: 'text', text: 'No knowledge items found.' }] };
|
|
161
|
+
}
|
|
162
|
+
const listing = items.map((k) => {
|
|
163
|
+
const icon = k.kind === 'folder' ? '📁' : '📄';
|
|
164
|
+
const meta = k.kind === 'folder'
|
|
165
|
+
? `${k.children_count || 0} items`
|
|
166
|
+
: `${k.file_size} bytes`;
|
|
167
|
+
return `- ${icon} **${k.name}** (ID: ${k._id}) [${k.kind}]\n ${k.description || 'No description'}\n ${meta} | Updated: ${k.updated_at}`;
|
|
168
|
+
}).join('\n\n');
|
|
169
|
+
return { content: [{ type: 'text', text: `Found ${items.length} knowledge item(s):\n\n${listing}` }] };
|
|
170
|
+
}
|
|
171
|
+
case 'read_knowledge': {
|
|
172
|
+
if (!args.knowledge_id) {
|
|
173
|
+
return { content: [{ type: 'text', text: 'Error: knowledge_id is required' }], isError: true };
|
|
174
|
+
}
|
|
175
|
+
const data = await apiCall(config, 'GET', `/agent-knowledge/${args.knowledge_id}`);
|
|
176
|
+
return {
|
|
177
|
+
content: [{
|
|
178
|
+
type: 'text',
|
|
179
|
+
text: `# ${data.name}\n\n${data.description ? `> ${data.description}\n\n` : ''}${data.content}`,
|
|
180
|
+
}],
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
case 'create_knowledge': {
|
|
184
|
+
const isFolder = args.kind === 'folder';
|
|
185
|
+
if (!args.name) {
|
|
186
|
+
return { content: [{ type: 'text', text: 'Error: name is required' }], isError: true };
|
|
187
|
+
}
|
|
188
|
+
if (!isFolder && !args.content) {
|
|
189
|
+
return { content: [{ type: 'text', text: 'Error: content is required for documents' }], isError: true };
|
|
190
|
+
}
|
|
191
|
+
const body = {
|
|
192
|
+
name: args.name,
|
|
193
|
+
agent_id: config.agentId,
|
|
194
|
+
};
|
|
195
|
+
if (args.kind)
|
|
196
|
+
body.kind = args.kind;
|
|
197
|
+
if (args.content)
|
|
198
|
+
body.content = args.content;
|
|
199
|
+
if (args.description)
|
|
200
|
+
body.description = args.description;
|
|
201
|
+
if (args.parent_id)
|
|
202
|
+
body.parent_id = args.parent_id;
|
|
203
|
+
const data = await apiCall(config, 'POST', '/agent-knowledge', body);
|
|
204
|
+
const label = isFolder ? 'Folder' : 'Knowledge';
|
|
205
|
+
return { content: [{ type: 'text', text: `${label} "${data.name}" created (ID: ${data._id})` }] };
|
|
206
|
+
}
|
|
207
|
+
case 'update_knowledge': {
|
|
208
|
+
if (!args.knowledge_id) {
|
|
209
|
+
return { content: [{ type: 'text', text: 'Error: knowledge_id is required' }], isError: true };
|
|
210
|
+
}
|
|
211
|
+
const body = {};
|
|
212
|
+
if (args.name)
|
|
213
|
+
body.name = args.name;
|
|
214
|
+
if (args.content)
|
|
215
|
+
body.content = args.content;
|
|
216
|
+
if (args.description)
|
|
217
|
+
body.description = args.description;
|
|
218
|
+
const data = await apiCall(config, 'PUT', `/agent-knowledge/${args.knowledge_id}`, body);
|
|
219
|
+
return { content: [{ type: 'text', text: `Knowledge "${data.name}" updated (ID: ${data._id})` }] };
|
|
220
|
+
}
|
|
221
|
+
default:
|
|
222
|
+
return { content: [{ type: 'text', text: `Unknown tool: ${toolName}` }], isError: true };
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
catch (err) {
|
|
226
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
227
|
+
return { content: [{ type: 'text', text: `Error: ${message}` }], isError: true };
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
// ===== Stdio Transport =====
|
|
231
|
+
function startServer(config) {
|
|
232
|
+
process.stderr.write(`[knowledge-mcp] Starting with agent ${config.agentId}\n`);
|
|
233
|
+
let buffer = '';
|
|
234
|
+
process.stdin.setEncoding('utf-8');
|
|
235
|
+
process.stdin.on('data', (chunk) => {
|
|
236
|
+
buffer += chunk;
|
|
237
|
+
// Claude Code sends newline-delimited JSON (one JSON object per line)
|
|
238
|
+
const lines = buffer.split('\n');
|
|
239
|
+
buffer = lines.pop() ?? ''; // Keep incomplete last line in buffer
|
|
240
|
+
for (const line of lines) {
|
|
241
|
+
const trimmed = line.trim();
|
|
242
|
+
if (!trimmed)
|
|
243
|
+
continue;
|
|
244
|
+
try {
|
|
245
|
+
const req = JSON.parse(trimmed);
|
|
246
|
+
handleRequest(config, req).catch((err) => {
|
|
247
|
+
process.stderr.write(`[knowledge-mcp] Error handling ${req.method}: ${err}\n`);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
catch {
|
|
251
|
+
process.stderr.write(`[knowledge-mcp] Failed to parse JSON: ${trimmed.slice(0, 100)}\n`);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
});
|
|
255
|
+
process.stdin.on('end', () => {
|
|
256
|
+
process.stderr.write('[knowledge-mcp] stdin closed, shutting down\n');
|
|
257
|
+
process.exit(0);
|
|
258
|
+
});
|
|
259
|
+
}
|
|
260
|
+
// ===== Main =====
|
|
261
|
+
const config = parseArgs();
|
|
262
|
+
startServer(config);
|
|
263
|
+
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { AgentConfig } from '../types/index.js';
|
|
2
|
+
/**
|
|
3
|
+
* Generate MCP server config file for Claude Code.
|
|
4
|
+
* This file is auto-detected by runClaude and passed via --mcp-config.
|
|
5
|
+
*/
|
|
6
|
+
export declare function setupMcpConfig(config: AgentConfig): string;
|
|
7
|
+
/**
|
|
8
|
+
* Get the MCP config path if it exists.
|
|
9
|
+
*/
|
|
10
|
+
export declare function getMcpConfigPath(): string | undefined;
|
|
11
|
+
/**
|
|
12
|
+
* Write MCP server config into agent's folder .mcp.json.
|
|
13
|
+
* Claude Code reads .mcp.json automatically for project-level MCPs.
|
|
14
|
+
* Note: .claude/settings.json mcpServers is NOT reliably read by Claude Code CLI.
|
|
15
|
+
*/
|
|
16
|
+
export declare function writeAgentFolderMcpConfig(agentFolderPath: string, config: AgentConfig): void;
|
|
17
|
+
/**
|
|
18
|
+
* Clean up MCP config file.
|
|
19
|
+
*/
|
|
20
|
+
export declare function cleanupMcpConfig(): void;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = path.dirname(__filename);
|
|
6
|
+
const MCP_CONFIG_DIR = path.join(process.env.HOME || '', '.tuna-agent');
|
|
7
|
+
const MCP_CONFIG_PATH = path.join(MCP_CONFIG_DIR, 'mcp-config.json');
|
|
8
|
+
/**
|
|
9
|
+
* Generate MCP server config file for Claude Code.
|
|
10
|
+
* This file is auto-detected by runClaude and passed via --mcp-config.
|
|
11
|
+
*/
|
|
12
|
+
export function setupMcpConfig(config) {
|
|
13
|
+
const knowledgeServerPath = path.join(__dirname, 'knowledge-server.js');
|
|
14
|
+
const browserServerPath = path.join(__dirname, 'browser-server.js');
|
|
15
|
+
const mcpConfig = {
|
|
16
|
+
mcpServers: {
|
|
17
|
+
'tuna-knowledge': {
|
|
18
|
+
command: process.execPath, // full path to current node binary
|
|
19
|
+
args: [
|
|
20
|
+
knowledgeServerPath,
|
|
21
|
+
'--api-url', config.apiUrl,
|
|
22
|
+
'--token', config.agentToken,
|
|
23
|
+
'--agent-id', config.agentId,
|
|
24
|
+
],
|
|
25
|
+
},
|
|
26
|
+
'tuna-browser': {
|
|
27
|
+
command: process.execPath,
|
|
28
|
+
args: [browserServerPath, '--user-data-dir', path.join(process.env.HOME || '', '.config', 'tuna-browser', 'chrome-profile')],
|
|
29
|
+
},
|
|
30
|
+
},
|
|
31
|
+
};
|
|
32
|
+
if (!fs.existsSync(MCP_CONFIG_DIR)) {
|
|
33
|
+
fs.mkdirSync(MCP_CONFIG_DIR, { recursive: true });
|
|
34
|
+
}
|
|
35
|
+
fs.writeFileSync(MCP_CONFIG_PATH, JSON.stringify(mcpConfig, null, 2));
|
|
36
|
+
console.log(`[MCP] Config written to ${MCP_CONFIG_PATH}`);
|
|
37
|
+
return MCP_CONFIG_PATH;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get the MCP config path if it exists.
|
|
41
|
+
*/
|
|
42
|
+
export function getMcpConfigPath() {
|
|
43
|
+
if (fs.existsSync(MCP_CONFIG_PATH)) {
|
|
44
|
+
return MCP_CONFIG_PATH;
|
|
45
|
+
}
|
|
46
|
+
return undefined;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Write MCP server config into agent's folder .mcp.json.
|
|
50
|
+
* Claude Code reads .mcp.json automatically for project-level MCPs.
|
|
51
|
+
* Note: .claude/settings.json mcpServers is NOT reliably read by Claude Code CLI.
|
|
52
|
+
*/
|
|
53
|
+
export function writeAgentFolderMcpConfig(agentFolderPath, config) {
|
|
54
|
+
const knowledgeServerPath = path.join(__dirname, 'knowledge-server.js');
|
|
55
|
+
const browserServerPath = path.join(__dirname, 'browser-server.js');
|
|
56
|
+
try {
|
|
57
|
+
const mcpJsonPath = path.join(agentFolderPath, '.mcp.json');
|
|
58
|
+
// Read existing .mcp.json to preserve other servers (e.g. playwright)
|
|
59
|
+
let existing = {};
|
|
60
|
+
if (fs.existsSync(mcpJsonPath)) {
|
|
61
|
+
existing = JSON.parse(fs.readFileSync(mcpJsonPath, 'utf-8'));
|
|
62
|
+
}
|
|
63
|
+
const existingServers = existing.mcpServers ?? {};
|
|
64
|
+
existing.mcpServers = {
|
|
65
|
+
...existingServers,
|
|
66
|
+
'tuna-knowledge': {
|
|
67
|
+
command: process.execPath,
|
|
68
|
+
args: [
|
|
69
|
+
knowledgeServerPath,
|
|
70
|
+
'--api-url', config.apiUrl,
|
|
71
|
+
'--token', config.agentToken,
|
|
72
|
+
'--agent-id', config.agentId,
|
|
73
|
+
],
|
|
74
|
+
},
|
|
75
|
+
'tuna-browser': {
|
|
76
|
+
command: process.execPath,
|
|
77
|
+
args: [browserServerPath, '--user-data-dir', path.join(process.env.HOME || '', '.config', 'tuna-browser', 'chrome-profile')],
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
fs.writeFileSync(mcpJsonPath, JSON.stringify(existing, null, 2));
|
|
81
|
+
console.log(`[MCP] Agent folder .mcp.json written to ${mcpJsonPath}`);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
console.warn(`[MCP] Failed to write agent folder .mcp.json:`, err);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Clean up MCP config file.
|
|
89
|
+
*/
|
|
90
|
+
export function cleanupMcpConfig() {
|
|
91
|
+
if (fs.existsSync(MCP_CONFIG_PATH)) {
|
|
92
|
+
fs.unlinkSync(MCP_CONFIG_PATH);
|
|
93
|
+
}
|
|
94
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -10,6 +10,7 @@ export interface AgentConfig {
|
|
|
10
10
|
agentConfig?: {
|
|
11
11
|
openclawGatewayUrl?: string;
|
|
12
12
|
};
|
|
13
|
+
extensionCodes?: string[];
|
|
13
14
|
}
|
|
14
15
|
export interface AgentCapabilities {
|
|
15
16
|
supports_teams: boolean;
|
|
@@ -41,6 +42,7 @@ export interface TaskAssignment {
|
|
|
41
42
|
confirmBeforeEdit?: boolean;
|
|
42
43
|
attachments?: ChatAttachment[];
|
|
43
44
|
source?: 'manual' | 'skill' | 'scheduled' | 'workflow' | 'api';
|
|
45
|
+
agentId?: string;
|
|
44
46
|
}
|
|
45
47
|
export interface ConnectResponse {
|
|
46
48
|
machine_id: string;
|
|
@@ -20,6 +20,8 @@ export interface ClaudeCliOptions {
|
|
|
20
20
|
onPermissionRequest?: (tool: string, detail: string) => Promise<boolean>;
|
|
21
21
|
/** Local file paths to pass as input (e.g., images) via --input-file flags */
|
|
22
22
|
inputFiles?: string[];
|
|
23
|
+
/** Path to MCP server config JSON file (passed via --mcp-config) */
|
|
24
|
+
mcpConfigPath?: string;
|
|
23
25
|
}
|
|
24
26
|
export interface ClaudeCliResult {
|
|
25
27
|
result: string;
|
package/dist/utils/claude-cli.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawn, execSync } from 'child_process';
|
|
2
2
|
import { StringDecoder } from 'string_decoder';
|
|
3
|
+
import { getMcpConfigPath } from '../mcp/setup.js';
|
|
3
4
|
/** Cached absolute path to `claude` binary, resolved once at first use */
|
|
4
5
|
let _claudeBinPath = null;
|
|
5
6
|
function getClaudeBinPath() {
|
|
@@ -101,6 +102,11 @@ export function runClaude(options) {
|
|
|
101
102
|
args.push('--tools', ''); // skip built-in tools
|
|
102
103
|
args.push('--disable-slash-commands'); // skip skills/plugins
|
|
103
104
|
}
|
|
105
|
+
// Auto-detect MCP config for knowledge server (skip if lightweight mode)
|
|
106
|
+
const mcpConfig = options.mcpConfigPath || (!options.lightweight ? getMcpConfigPath() : undefined);
|
|
107
|
+
if (mcpConfig) {
|
|
108
|
+
args.push('--mcp-config', mcpConfig);
|
|
109
|
+
}
|
|
104
110
|
if (options.permissionMode) {
|
|
105
111
|
args.push('--permission-mode', options.permissionMode);
|
|
106
112
|
}
|
|
@@ -111,6 +117,7 @@ export function runClaude(options) {
|
|
|
111
117
|
}
|
|
112
118
|
}
|
|
113
119
|
const useInteractiveStdin = !!options.permissionMode && !!options.onPermissionRequest;
|
|
120
|
+
console.log(`[claude-cli] Spawning with args: ${args.filter(a => !a.startsWith('sk-') && a.length < 200).join(' ')}`);
|
|
114
121
|
const claudeBin = getClaudeBinPath();
|
|
115
122
|
// Ensure PATH includes common bin dirs so shebang `#!/usr/bin/env node` resolves
|
|
116
123
|
const spawnPath = [
|
|
@@ -120,18 +127,27 @@ export function runClaude(options) {
|
|
|
120
127
|
`${process.env.HOME}/.local/bin`,
|
|
121
128
|
`${process.env.HOME}/.nvm/versions/node/v20.10.0/bin`,
|
|
122
129
|
].filter(Boolean).join(':');
|
|
130
|
+
// Build env: inherit process.env but strip Claude Code session markers
|
|
131
|
+
// so the spawned claude process is not blocked by nested-session detection.
|
|
132
|
+
const spawnEnv = {};
|
|
133
|
+
for (const [k, v] of Object.entries(process.env)) {
|
|
134
|
+
if (v !== undefined)
|
|
135
|
+
spawnEnv[k] = v;
|
|
136
|
+
}
|
|
137
|
+
// Remove nested-session markers
|
|
138
|
+
delete spawnEnv['CLAUDECODE'];
|
|
139
|
+
delete spawnEnv['CLAUDE_CODE_ENTRYPOINT'];
|
|
140
|
+
delete spawnEnv['CLAUDE_CODE_ENABLE_SDK_FILE_CHECKPOINTING'];
|
|
141
|
+
spawnEnv['HOME'] = process.env.HOME || '';
|
|
142
|
+
spawnEnv['PATH'] = spawnPath;
|
|
143
|
+
if (options.agentTeam) {
|
|
144
|
+
spawnEnv['CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS'] = '1';
|
|
145
|
+
spawnEnv['CLAUDE_CODE_TEAMMATE_MODE'] = 'in-process';
|
|
146
|
+
}
|
|
123
147
|
const proc = spawn(claudeBin, args, {
|
|
124
148
|
cwd: options.cwd,
|
|
125
149
|
stdio: [useInteractiveStdin ? 'pipe' : 'ignore', 'pipe', 'pipe'],
|
|
126
|
-
env:
|
|
127
|
-
...process.env,
|
|
128
|
-
HOME: process.env.HOME || '',
|
|
129
|
-
PATH: spawnPath,
|
|
130
|
-
...(options.agentTeam ? {
|
|
131
|
-
CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS: '1',
|
|
132
|
-
CLAUDE_CODE_TEAMMATE_MODE: 'in-process',
|
|
133
|
-
} : {}),
|
|
134
|
-
},
|
|
150
|
+
env: spawnEnv,
|
|
135
151
|
});
|
|
136
152
|
// 30-minute timeout by default
|
|
137
153
|
const timeoutMs = options.timeoutMs ?? 30 * 60 * 1000;
|
|
@@ -198,6 +214,10 @@ export function runClaude(options) {
|
|
|
198
214
|
proc.stderr.on('data', (chunk) => {
|
|
199
215
|
const text = stderrDecoder.write(chunk);
|
|
200
216
|
stderr += text;
|
|
217
|
+
// Log stderr for debugging (MCP server startup errors, etc.)
|
|
218
|
+
if (text.trim()) {
|
|
219
|
+
console.log(`[claude-cli] stderr: ${text.trim().substring(0, 300)}`);
|
|
220
|
+
}
|
|
201
221
|
// Detect permission prompts from Claude Code on stderr
|
|
202
222
|
if (useInteractiveStdin) {
|
|
203
223
|
handlePermissionPrompt(text, proc, options.onPermissionRequest);
|
|
@@ -29,10 +29,13 @@ export declare const CommandMessageSchema: z.ZodObject<{
|
|
|
29
29
|
type: z.ZodLiteral<"command">;
|
|
30
30
|
command: z.ZodString;
|
|
31
31
|
workspace_path: z.ZodOptional<z.ZodString>;
|
|
32
|
+
agent_folders: z.ZodOptional<z.ZodArray<z.ZodString>>;
|
|
33
|
+
agent_id: z.ZodOptional<z.ZodString>;
|
|
34
|
+
folder_path: z.ZodOptional<z.ZodString>;
|
|
32
35
|
skill_id: z.ZodOptional<z.ZodString>;
|
|
33
36
|
source_path: z.ZodOptional<z.ZodString>;
|
|
34
37
|
skill_name: z.ZodOptional<z.ZodString>;
|
|
35
|
-
}, z.core.$
|
|
38
|
+
}, z.core.$loose>;
|
|
36
39
|
export declare const TaskCancelledSchema: z.ZodObject<{
|
|
37
40
|
type: z.ZodLiteral<"task_cancelled">;
|
|
38
41
|
taskId: z.ZodString;
|
|
@@ -21,11 +21,16 @@ export const CommandMessageSchema = z.object({
|
|
|
21
21
|
type: z.literal('command'),
|
|
22
22
|
command: z.string().min(1),
|
|
23
23
|
workspace_path: z.string().optional(),
|
|
24
|
+
// For scan_workspace command
|
|
25
|
+
agent_folders: z.array(z.string()).optional(),
|
|
26
|
+
// For rescan_agent_skills command
|
|
27
|
+
agent_id: z.string().optional(),
|
|
28
|
+
folder_path: z.string().optional(),
|
|
24
29
|
// For analyze_skill command
|
|
25
30
|
skill_id: z.string().optional(),
|
|
26
31
|
source_path: z.string().optional(),
|
|
27
32
|
skill_name: z.string().optional(),
|
|
28
|
-
});
|
|
33
|
+
}).passthrough();
|
|
29
34
|
export const TaskCancelledSchema = z.object({
|
|
30
35
|
type: z.literal('task_cancelled'),
|
|
31
36
|
taskId: z.string().min(1),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tuna-agent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.2",
|
|
4
4
|
"description": "Tuna Agent - Run AI coding tasks on your machine",
|
|
5
5
|
"bin": {
|
|
6
6
|
"tuna-agent": "dist/cli/index.js"
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"chalk": "^5.4.0",
|
|
18
18
|
"commander": "^13.0.0",
|
|
19
|
+
"playwright-core": "^1.58.2",
|
|
19
20
|
"ws": "^8.18.0",
|
|
20
21
|
"zod": "^4.3.6"
|
|
21
22
|
},
|