protoagent 0.1.10 → 0.1.11
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 +0 -1
- package/dist/App.js +113 -80
- package/dist/agentic-loop.js +182 -31
- package/dist/cli.js +3 -3
- package/dist/config.js +76 -22
- package/dist/mcp.js +15 -0
- package/dist/providers.js +8 -15
- package/dist/sessions.js +13 -3
- package/dist/skills.js +2 -1
- package/dist/sub-agent.js +138 -20
- package/dist/system-prompt.js +45 -0
- package/dist/tools/bash.js +1 -1
- package/dist/tools/index.js +1 -1
- package/dist/utils/approval.js +8 -8
- package/dist/utils/cost-tracker.js +9 -3
- package/dist/utils/file-time.js +0 -9
- package/package.json +23 -3
package/dist/sub-agent.js
CHANGED
|
@@ -28,7 +28,7 @@ export const subAgentTool = {
|
|
|
28
28
|
},
|
|
29
29
|
max_iterations: {
|
|
30
30
|
type: 'number',
|
|
31
|
-
description: 'Maximum tool-call iterations for the sub-agent. Defaults to
|
|
31
|
+
description: 'Maximum tool-call iterations for the sub-agent. Defaults to 500.',
|
|
32
32
|
},
|
|
33
33
|
},
|
|
34
34
|
required: ['task'],
|
|
@@ -39,7 +39,7 @@ export const subAgentTool = {
|
|
|
39
39
|
* Run a sub-agent with its own isolated conversation.
|
|
40
40
|
* Returns the sub-agent's final text response.
|
|
41
41
|
*/
|
|
42
|
-
export async function runSubAgent(client, model, task, maxIterations =
|
|
42
|
+
export async function runSubAgent(client, model, task, maxIterations = 500, requestDefaults = {}, onProgress, abortSignal, pricing) {
|
|
43
43
|
const op = logger.startOperation('sub-agent');
|
|
44
44
|
const subAgentSessionId = `sub-agent-${crypto.randomUUID()}`;
|
|
45
45
|
const systemPrompt = await generateSystemPrompt();
|
|
@@ -54,35 +54,142 @@ Do NOT ask the user questions — work autonomously with the tools available.`;
|
|
|
54
54
|
{ role: 'system', content: subSystemPrompt },
|
|
55
55
|
{ role: 'user', content: task },
|
|
56
56
|
];
|
|
57
|
+
// Track cumulative usage across all API calls in the sub-agent
|
|
58
|
+
let totalInputTokens = 0;
|
|
59
|
+
let totalOutputTokens = 0;
|
|
60
|
+
let totalCost = 0;
|
|
57
61
|
try {
|
|
58
62
|
for (let i = 0; i < maxIterations; i++) {
|
|
59
63
|
// Check abort at the top of each iteration
|
|
60
64
|
if (abortSignal?.aborted) {
|
|
61
|
-
return '(sub-agent aborted)';
|
|
65
|
+
return { response: '(sub-agent aborted)', usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, cost: totalCost } };
|
|
62
66
|
}
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
let assistantMessage;
|
|
68
|
+
let hasToolCalls = false;
|
|
69
|
+
try {
|
|
70
|
+
const stream = await client.chat.completions.create({
|
|
71
|
+
...requestDefaults,
|
|
72
|
+
model,
|
|
73
|
+
messages,
|
|
74
|
+
tools: getAllTools(),
|
|
75
|
+
tool_choice: 'auto',
|
|
76
|
+
stream: true,
|
|
77
|
+
stream_options: { include_usage: true },
|
|
78
|
+
}, { signal: abortSignal });
|
|
79
|
+
// Accumulate the streamed response
|
|
80
|
+
assistantMessage = {
|
|
81
|
+
role: 'assistant',
|
|
82
|
+
content: '',
|
|
83
|
+
tool_calls: [],
|
|
84
|
+
};
|
|
85
|
+
let streamedContent = '';
|
|
86
|
+
hasToolCalls = false;
|
|
87
|
+
let actualUsage;
|
|
88
|
+
for await (const chunk of stream) {
|
|
89
|
+
const delta = chunk.choices[0]?.delta;
|
|
90
|
+
if (chunk.usage) {
|
|
91
|
+
actualUsage = chunk.usage;
|
|
92
|
+
}
|
|
93
|
+
// Stream text content
|
|
94
|
+
if (delta?.content) {
|
|
95
|
+
streamedContent += delta.content;
|
|
96
|
+
assistantMessage.content = streamedContent;
|
|
97
|
+
}
|
|
98
|
+
// Accumulate tool calls across stream chunks
|
|
99
|
+
if (delta?.tool_calls) {
|
|
100
|
+
hasToolCalls = true;
|
|
101
|
+
for (const tc of delta.tool_calls) {
|
|
102
|
+
const idx = tc.index || 0;
|
|
103
|
+
if (!assistantMessage.tool_calls[idx]) {
|
|
104
|
+
assistantMessage.tool_calls[idx] = {
|
|
105
|
+
id: '',
|
|
106
|
+
type: 'function',
|
|
107
|
+
function: { name: '', arguments: '' },
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
if (tc.id)
|
|
111
|
+
assistantMessage.tool_calls[idx].id = tc.id;
|
|
112
|
+
if (tc.function?.name) {
|
|
113
|
+
assistantMessage.tool_calls[idx].function.name += tc.function.name;
|
|
114
|
+
}
|
|
115
|
+
if (tc.function?.arguments) {
|
|
116
|
+
assistantMessage.tool_calls[idx].function.arguments += tc.function.arguments;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
// Accumulate usage for this iteration
|
|
122
|
+
const iterationInputTokens = actualUsage?.prompt_tokens || 0;
|
|
123
|
+
const iterationOutputTokens = actualUsage?.completion_tokens || 0;
|
|
124
|
+
totalInputTokens += iterationInputTokens;
|
|
125
|
+
totalOutputTokens += iterationOutputTokens;
|
|
126
|
+
// Calculate cost if pricing is available
|
|
127
|
+
if (pricing && (iterationInputTokens > 0 || iterationOutputTokens > 0)) {
|
|
128
|
+
const cachedTokens = actualUsage?.prompt_tokens_details?.cached_tokens;
|
|
129
|
+
if (cachedTokens && cachedTokens > 0 && pricing.cachedPerToken != null) {
|
|
130
|
+
const uncachedTokens = iterationInputTokens - cachedTokens;
|
|
131
|
+
totalCost += uncachedTokens * pricing.inputPerToken + cachedTokens * pricing.cachedPerToken + iterationOutputTokens * pricing.outputPerToken;
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
totalCost += iterationInputTokens * pricing.inputPerToken + iterationOutputTokens * pricing.outputPerToken;
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
catch (err) {
|
|
139
|
+
// If aborted during streaming, return gracefully
|
|
140
|
+
if (abortSignal?.aborted || (err instanceof Error && (err.name === 'AbortError' || err.message === 'Operation aborted'))) {
|
|
141
|
+
logger.debug('Sub-agent aborted during streaming');
|
|
142
|
+
return { response: '(sub-agent aborted)', usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, cost: totalCost } };
|
|
143
|
+
}
|
|
144
|
+
throw err;
|
|
145
|
+
}
|
|
146
|
+
const message = assistantMessage;
|
|
71
147
|
if (!message)
|
|
72
148
|
break;
|
|
73
149
|
// Check for tool calls
|
|
74
|
-
if (
|
|
75
|
-
|
|
76
|
-
|
|
150
|
+
if (hasToolCalls && assistantMessage.tool_calls.length > 0) {
|
|
151
|
+
// Clean up empty tool_calls entries (from sparse array)
|
|
152
|
+
assistantMessage.tool_calls = assistantMessage.tool_calls.filter(Boolean);
|
|
153
|
+
// Filter out tool calls with malformed JSON arguments (can happen if stream aborted mid-tool-call)
|
|
154
|
+
assistantMessage.tool_calls = assistantMessage.tool_calls.filter((tc) => {
|
|
155
|
+
const args = tc.function?.arguments;
|
|
156
|
+
if (!args)
|
|
157
|
+
return true; // No args is valid
|
|
158
|
+
try {
|
|
159
|
+
JSON.parse(args);
|
|
160
|
+
return true;
|
|
161
|
+
}
|
|
162
|
+
catch {
|
|
163
|
+
logger.warn('Filtering out sub-agent tool call with malformed JSON', {
|
|
164
|
+
tool: tc.function?.name,
|
|
165
|
+
argsPreview: args.slice(0, 100),
|
|
166
|
+
});
|
|
167
|
+
return false;
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
// Only add message if we have valid tool calls
|
|
171
|
+
if (assistantMessage.tool_calls.length === 0) {
|
|
172
|
+
hasToolCalls = false;
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
messages.push(message);
|
|
176
|
+
}
|
|
177
|
+
for (const toolCall of assistantMessage.tool_calls) {
|
|
77
178
|
// Check abort between tool calls
|
|
78
179
|
if (abortSignal?.aborted) {
|
|
79
|
-
return '(sub-agent aborted)';
|
|
180
|
+
return { response: '(sub-agent aborted)', usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, cost: totalCost } };
|
|
80
181
|
}
|
|
81
182
|
const { name, arguments: argsStr } = toolCall.function;
|
|
82
|
-
|
|
83
|
-
|
|
183
|
+
let args;
|
|
184
|
+
try {
|
|
185
|
+
args = JSON.parse(argsStr);
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
args = {};
|
|
189
|
+
}
|
|
190
|
+
logger.debug(`Sub-agent tool call: ${name}`, { args });
|
|
191
|
+
onProgress?.({ tool: name, status: 'running', iteration: i, args });
|
|
84
192
|
try {
|
|
85
|
-
const args = JSON.parse(argsStr);
|
|
86
193
|
const result = await handleToolCall(name, args, { sessionId: subAgentSessionId, abortSignal });
|
|
87
194
|
messages.push({
|
|
88
195
|
role: 'tool',
|
|
@@ -104,9 +211,20 @@ Do NOT ask the user questions — work autonomously with the tools available.`;
|
|
|
104
211
|
continue;
|
|
105
212
|
}
|
|
106
213
|
// Plain text response — we're done
|
|
107
|
-
|
|
214
|
+
if (message.content) {
|
|
215
|
+
messages.push({
|
|
216
|
+
role: 'assistant',
|
|
217
|
+
content: message.content,
|
|
218
|
+
});
|
|
219
|
+
return { response: message.content, usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, cost: totalCost } };
|
|
220
|
+
}
|
|
221
|
+
// The model produced an empty text response (e.g. it only called tools
|
|
222
|
+
// and issued no final summary). Log it and return a sentinel so the
|
|
223
|
+
// parent agent knows the sub-agent finished but had nothing to say.
|
|
224
|
+
logger.debug('Sub-agent returned empty content', { iteration: i });
|
|
225
|
+
return { response: '(sub-agent completed with no response)', usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, cost: totalCost } };
|
|
108
226
|
}
|
|
109
|
-
return '(sub-agent reached iteration limit)';
|
|
227
|
+
return { response: '(sub-agent reached iteration limit)', usage: { inputTokens: totalInputTokens, outputTokens: totalOutputTokens, cost: totalCost } };
|
|
110
228
|
}
|
|
111
229
|
finally {
|
|
112
230
|
op.end();
|
package/dist/system-prompt.js
CHANGED
|
@@ -6,12 +6,45 @@
|
|
|
6
6
|
* - Working directory and project structure
|
|
7
7
|
* - Tool descriptions (auto-generated from tool schemas)
|
|
8
8
|
* - Skills catalog (loaded progressively from skill directories)
|
|
9
|
+
* - AGENTS.md content (custom instructions for the agent)
|
|
9
10
|
* - Guidelines for file operations, TODO tracking, etc.
|
|
10
11
|
*/
|
|
11
12
|
import fs from 'node:fs/promises';
|
|
12
13
|
import path from 'node:path';
|
|
13
14
|
import { getAllTools } from './tools/index.js';
|
|
14
15
|
import { buildSkillsCatalogSection, initializeSkillsSupport } from './skills.js';
|
|
16
|
+
import { getActiveRuntimeConfigPath } from './runtime-config.js';
|
|
17
|
+
/**
|
|
18
|
+
* Load AGENTS.md content from cwd and parent directories.
|
|
19
|
+
*
|
|
20
|
+
* AGENTS.md (https://agents.md/) is a simple, open format for guiding coding agents.
|
|
21
|
+
* It's like a README for agents — a dedicated place to give AI coding tools the
|
|
22
|
+
* context they need to work on a project.
|
|
23
|
+
*
|
|
24
|
+
* The lookup is hierarchical:
|
|
25
|
+
* - Checks cwd, then parent directories up to the filesystem root
|
|
26
|
+
* - First AGENTS.md found wins
|
|
27
|
+
* - Returns null if no AGENTS.md is found
|
|
28
|
+
*/
|
|
29
|
+
async function loadAgentsMd() {
|
|
30
|
+
let currentDir = path.resolve('.');
|
|
31
|
+
while (true) {
|
|
32
|
+
const agentsPath = path.join(currentDir, 'AGENTS.md');
|
|
33
|
+
try {
|
|
34
|
+
await fs.access(agentsPath);
|
|
35
|
+
const content = await fs.readFile(agentsPath, 'utf-8');
|
|
36
|
+
return { content, path: agentsPath };
|
|
37
|
+
}
|
|
38
|
+
catch {
|
|
39
|
+
// File doesn't exist here — check parent
|
|
40
|
+
}
|
|
41
|
+
const parentDir = path.dirname(currentDir);
|
|
42
|
+
if (parentDir === currentDir)
|
|
43
|
+
break; // Reached filesystem root
|
|
44
|
+
currentDir = parentDir;
|
|
45
|
+
}
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
15
48
|
/** Build a filtered directory tree (depth 3, excludes noise). */
|
|
16
49
|
async function buildDirectoryTree(dirPath = '.', depth = 0, maxDepth = 3) {
|
|
17
50
|
if (depth > maxDepth)
|
|
@@ -66,6 +99,11 @@ export async function generateSystemPrompt() {
|
|
|
66
99
|
const skills = await initializeSkillsSupport();
|
|
67
100
|
const toolDescriptions = generateToolDescriptions();
|
|
68
101
|
const skillsSection = buildSkillsCatalogSection(skills);
|
|
102
|
+
const configPath = getActiveRuntimeConfigPath();
|
|
103
|
+
const agentsMd = await loadAgentsMd();
|
|
104
|
+
const agentsMdSection = agentsMd
|
|
105
|
+
? `\nAGENTS.md INSTRUCTIONS\n\nThe following instructions are from the AGENTS.md file at: ${agentsMd.path}\n\n${agentsMd.content}\n`
|
|
106
|
+
: '';
|
|
69
107
|
return `You are ProtoAgent, a coding assistant with file system and shell command capabilities.
|
|
70
108
|
Your job is to help the user complete coding tasks in their project. You must be absolutely careful and diligent in your work, and follow all guidelines to the letter. Always prefer thoroughness and correctness over speed. Never cut corners.
|
|
71
109
|
|
|
@@ -73,9 +111,16 @@ PROJECT CONTEXT
|
|
|
73
111
|
|
|
74
112
|
Working Directory: ${cwd}
|
|
75
113
|
Project Name: ${projectName}
|
|
114
|
+
Configuration Path: ${configPath || 'none (using defaults)'}
|
|
76
115
|
|
|
77
116
|
PROJECT STRUCTURE:
|
|
78
117
|
${tree}
|
|
118
|
+
${agentsMdSection}
|
|
119
|
+
PROTOAGENT DOCUMENTATION
|
|
120
|
+
|
|
121
|
+
ProtoAgent is a build-your-own coding agent — a lean, readable implementation that gives you the blueprint to understand and build your own AI coding assistant.
|
|
122
|
+
|
|
123
|
+
Configuration guide: https://protoagent.dev/guide/configuration
|
|
79
124
|
|
|
80
125
|
AVAILABLE TOOLS
|
|
81
126
|
|
package/dist/tools/bash.js
CHANGED
|
@@ -27,7 +27,7 @@ export const bashTool = {
|
|
|
27
27
|
},
|
|
28
28
|
},
|
|
29
29
|
};
|
|
30
|
-
// Hard-blocked commands — these CANNOT be run, even with --dangerously-
|
|
30
|
+
// Hard-blocked commands — these CANNOT be run, even with --dangerously-skip-permissions
|
|
31
31
|
const DANGEROUS_PATTERNS = [
|
|
32
32
|
'rm -rf /',
|
|
33
33
|
'sudo',
|
package/dist/tools/index.js
CHANGED
|
@@ -16,7 +16,7 @@ import { searchFilesTool, searchFiles } from './search-files.js';
|
|
|
16
16
|
import { bashTool, runBash } from './bash.js';
|
|
17
17
|
import { todoReadTool, todoWriteTool, readTodos, writeTodos } from './todo.js';
|
|
18
18
|
import { webfetchTool, webfetch } from './webfetch.js';
|
|
19
|
-
export {
|
|
19
|
+
export { setDangerouslySkipPermissions, setApprovalHandler, clearApprovalHandler } from '../utils/approval.js';
|
|
20
20
|
// All tool definitions — passed to the LLM
|
|
21
21
|
export const tools = [
|
|
22
22
|
readFileTool,
|
package/dist/utils/approval.js
CHANGED
|
@@ -8,21 +8,21 @@
|
|
|
8
8
|
* Approval can be granted:
|
|
9
9
|
* - Per-operation (one-time)
|
|
10
10
|
* - Per-operation-type for the session (e.g., "approve all writes")
|
|
11
|
-
* - Globally via --dangerously-
|
|
11
|
+
* - Globally via --dangerously-skip-permissions
|
|
12
12
|
*
|
|
13
13
|
* In the Ink UI, approvals are handled by emitting an event and waiting
|
|
14
14
|
* for the UI to resolve it (instead of blocking on stdin with inquirer).
|
|
15
15
|
*/
|
|
16
16
|
// Global state
|
|
17
|
-
let
|
|
17
|
+
let dangerouslySkipPermissions = false;
|
|
18
18
|
const sessionApprovals = new Set(); // stores approval keys scoped by session
|
|
19
19
|
// Callback that the Ink UI provides to handle interactive approval
|
|
20
20
|
let approvalHandler = null;
|
|
21
|
-
export function
|
|
22
|
-
|
|
21
|
+
export function setDangerouslySkipPermissions(value) {
|
|
22
|
+
dangerouslySkipPermissions = value;
|
|
23
23
|
}
|
|
24
|
-
export function
|
|
25
|
-
return
|
|
24
|
+
export function isDangerouslySkipPermissions() {
|
|
25
|
+
return dangerouslySkipPermissions;
|
|
26
26
|
}
|
|
27
27
|
export function setApprovalHandler(handler) {
|
|
28
28
|
approvalHandler = handler;
|
|
@@ -42,13 +42,13 @@ function getApprovalScopeKey(req) {
|
|
|
42
42
|
* Request approval for an operation. Returns true if approved.
|
|
43
43
|
*
|
|
44
44
|
* Check order:
|
|
45
|
-
* 1. --dangerously-
|
|
45
|
+
* 1. --dangerously-skip-permissions → auto-approve
|
|
46
46
|
* 2. Session approval for this type → auto-approve
|
|
47
47
|
* 3. Interactive prompt via the UI handler
|
|
48
48
|
* 4. No handler registered → reject (fail closed)
|
|
49
49
|
*/
|
|
50
50
|
export async function requestApproval(req) {
|
|
51
|
-
if (
|
|
51
|
+
if (dangerouslySkipPermissions)
|
|
52
52
|
return true;
|
|
53
53
|
const sessionKey = getApprovalScopeKey(req);
|
|
54
54
|
if (sessionApprovals.has(sessionKey))
|
|
@@ -26,7 +26,13 @@ export function estimateConversationTokens(messages) {
|
|
|
26
26
|
return messages.reduce((sum, m) => sum + estimateMessageTokens(m), 0) + 10;
|
|
27
27
|
}
|
|
28
28
|
/** Calculate dollar cost for a given number of tokens. */
|
|
29
|
-
export function calculateCost(inputTokens, outputTokens, pricing) {
|
|
29
|
+
export function calculateCost(inputTokens, outputTokens, pricing, cachedTokens) {
|
|
30
|
+
if (cachedTokens && cachedTokens > 0 && pricing.cachedPerToken != null) {
|
|
31
|
+
const uncachedTokens = inputTokens - cachedTokens;
|
|
32
|
+
return (uncachedTokens * pricing.inputPerToken +
|
|
33
|
+
cachedTokens * pricing.cachedPerToken +
|
|
34
|
+
outputTokens * pricing.outputPerToken);
|
|
35
|
+
}
|
|
30
36
|
return inputTokens * pricing.inputPerToken + outputTokens * pricing.outputPerToken;
|
|
31
37
|
}
|
|
32
38
|
/** Get context window utilisation info. */
|
|
@@ -42,11 +48,11 @@ export function getContextInfo(messages, pricing) {
|
|
|
42
48
|
};
|
|
43
49
|
}
|
|
44
50
|
/** Build a UsageInfo from actual or estimated token counts. */
|
|
45
|
-
export function createUsageInfo(inputTokens, outputTokens, pricing) {
|
|
51
|
+
export function createUsageInfo(inputTokens, outputTokens, pricing, cachedTokens) {
|
|
46
52
|
return {
|
|
47
53
|
inputTokens,
|
|
48
54
|
outputTokens,
|
|
49
55
|
totalTokens: inputTokens + outputTokens,
|
|
50
|
-
estimatedCost: calculateCost(inputTokens, outputTokens, pricing),
|
|
56
|
+
estimatedCost: calculateCost(inputTokens, outputTokens, pricing, cachedTokens),
|
|
51
57
|
};
|
|
52
58
|
}
|
package/dist/utils/file-time.js
CHANGED
|
@@ -42,15 +42,6 @@ export function checkReadBefore(sessionId, absolutePath) {
|
|
|
42
42
|
}
|
|
43
43
|
return null;
|
|
44
44
|
}
|
|
45
|
-
/**
|
|
46
|
-
* @deprecated Use checkReadBefore instead — it returns a string rather than
|
|
47
|
-
* throwing, so the error surfaces cleanly as a tool result.
|
|
48
|
-
*/
|
|
49
|
-
export function assertReadBefore(sessionId, absolutePath) {
|
|
50
|
-
const err = checkReadBefore(sessionId, absolutePath);
|
|
51
|
-
if (err)
|
|
52
|
-
throw new Error(err);
|
|
53
|
-
}
|
|
54
45
|
/**
|
|
55
46
|
* Clear all read-time entries for a session (e.g. on session end).
|
|
56
47
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "protoagent",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.11",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"files": [
|
|
6
6
|
"dist",
|
|
@@ -20,8 +20,28 @@
|
|
|
20
20
|
"docs:build": "vitepress build docs",
|
|
21
21
|
"docs:preview": "vitepress preview docs"
|
|
22
22
|
},
|
|
23
|
-
"author": "",
|
|
24
|
-
"license": "
|
|
23
|
+
"author": "Thomas Gauvin",
|
|
24
|
+
"license": "MIT",
|
|
25
|
+
"homepage": "https://protoagent.dev",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/thomasgauvin/protoagent.git"
|
|
29
|
+
},
|
|
30
|
+
"keywords": [
|
|
31
|
+
"ai",
|
|
32
|
+
"agent",
|
|
33
|
+
"cli",
|
|
34
|
+
"coding-agent",
|
|
35
|
+
"llm",
|
|
36
|
+
"openai",
|
|
37
|
+
"anthropic",
|
|
38
|
+
"gemini",
|
|
39
|
+
"terminal",
|
|
40
|
+
"typescript"
|
|
41
|
+
],
|
|
42
|
+
"engines": {
|
|
43
|
+
"node": ">=20"
|
|
44
|
+
},
|
|
25
45
|
"dependencies": {
|
|
26
46
|
"@inkjs/ui": "^2.0.0",
|
|
27
47
|
"@modelcontextprotocol/sdk": "^1.27.1",
|