ikie-cli 0.1.32 → 0.1.34
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 +185 -22
- package/dist/agent.d.ts +44 -0
- package/dist/agent.js +386 -128
- package/dist/config.d.ts +8 -0
- package/dist/config.js +4 -0
- package/dist/index.js +36 -1
- package/dist/mcp-manager.d.ts +75 -89
- package/dist/mcp-manager.js +710 -304
- package/dist/repl.js +315 -71
- package/dist/skills.d.ts +16 -0
- package/dist/skills.js +83 -6
- package/dist/theme.d.ts +1 -1
- package/dist/theme.js +21 -4
- package/dist/tools.d.ts +1 -0
- package/dist/tools.js +177 -166
- package/dist/tree.d.ts +19 -0
- package/dist/tree.js +266 -0
- package/package.json +2 -1
package/dist/skills.js
CHANGED
|
@@ -11,16 +11,38 @@ function parseFrontmatter(raw) {
|
|
|
11
11
|
if (!m)
|
|
12
12
|
return { meta: {}, body: text.trim() };
|
|
13
13
|
const meta = {};
|
|
14
|
-
|
|
14
|
+
const lines = m[1].split(/\r?\n/);
|
|
15
|
+
let i = 0;
|
|
16
|
+
while (i < lines.length) {
|
|
17
|
+
const line = lines[i];
|
|
15
18
|
const kv = /^([A-Za-z0-9_-]+)\s*:\s*(.*)$/.exec(line);
|
|
16
|
-
if (!kv)
|
|
19
|
+
if (!kv) {
|
|
20
|
+
i++;
|
|
17
21
|
continue;
|
|
22
|
+
}
|
|
23
|
+
const key = kv[1].toLowerCase();
|
|
18
24
|
let value = kv[2].trim();
|
|
25
|
+
// YAML list continuation: values that end without content may start a block list.
|
|
26
|
+
if (value === '') {
|
|
27
|
+
const listValues = [];
|
|
28
|
+
i++;
|
|
29
|
+
while (i < lines.length && /^\s*-\s+/.test(lines[i])) {
|
|
30
|
+
listValues.push(lines[i].replace(/^\s*-\s+/, '').trim());
|
|
31
|
+
i++;
|
|
32
|
+
}
|
|
33
|
+
if (listValues.length) {
|
|
34
|
+
meta[key] = listValues.join(', ');
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
meta[key] = '';
|
|
38
|
+
continue;
|
|
39
|
+
}
|
|
19
40
|
// Strip matching surrounding quotes.
|
|
20
41
|
if ((value.startsWith('"') && value.endsWith('"')) || (value.startsWith("'") && value.endsWith("'"))) {
|
|
21
42
|
value = value.slice(1, -1);
|
|
22
43
|
}
|
|
23
|
-
meta[
|
|
44
|
+
meta[key] = value;
|
|
45
|
+
i++;
|
|
24
46
|
}
|
|
25
47
|
return { meta, body: (m[2] ?? '').trim() };
|
|
26
48
|
}
|
|
@@ -87,6 +109,14 @@ export function discoverSkills(cwd = process.cwd()) {
|
|
|
87
109
|
const key = name.toLowerCase();
|
|
88
110
|
if (!name || byName.has(key))
|
|
89
111
|
continue;
|
|
112
|
+
const allowedTools = parseAllowedTools(meta['allowed-tools'] || meta.allowedtools || '');
|
|
113
|
+
const disallowedTools = parseAllowedTools(meta['disallowed-tools'] || meta.disallowedtools || '');
|
|
114
|
+
const disableModelInvocation = truthy(meta['disable-model-invocation'] || meta.disablemodelinvocation);
|
|
115
|
+
const userInvocable = truthy(meta['user-invocable'] || meta.userinvocable);
|
|
116
|
+
const whenToUse = (meta['when_to_use'] || meta['when-to-use'] || meta.whentouse || '').trim();
|
|
117
|
+
// Skills marked disable-model-invocation are not offered to the model.
|
|
118
|
+
if (disableModelInvocation)
|
|
119
|
+
continue;
|
|
90
120
|
byName.set(key, {
|
|
91
121
|
name,
|
|
92
122
|
description,
|
|
@@ -95,6 +125,11 @@ export function discoverSkills(cwd = process.cwd()) {
|
|
|
95
125
|
file,
|
|
96
126
|
source: root.source,
|
|
97
127
|
origin: root.origin,
|
|
128
|
+
allowedTools,
|
|
129
|
+
disallowedTools,
|
|
130
|
+
disableModelInvocation,
|
|
131
|
+
userInvocable,
|
|
132
|
+
whenToUse,
|
|
98
133
|
});
|
|
99
134
|
}
|
|
100
135
|
}
|
|
@@ -141,20 +176,62 @@ export function listSkillFiles(skill) {
|
|
|
141
176
|
walk(skill.dir, '');
|
|
142
177
|
return out.sort();
|
|
143
178
|
}
|
|
179
|
+
function truthy(v) {
|
|
180
|
+
return /^(true|yes|1|on)$/i.test((v ?? '').trim());
|
|
181
|
+
}
|
|
182
|
+
function parseAllowedTools(raw) {
|
|
183
|
+
const v = raw.trim();
|
|
184
|
+
if (!v)
|
|
185
|
+
return undefined;
|
|
186
|
+
return v.split(/[,\s]+/).map(t => t.trim()).filter(Boolean);
|
|
187
|
+
}
|
|
144
188
|
/**
|
|
145
189
|
* Render the skill catalog for the system prompt: each skill's name +
|
|
146
190
|
* description, so the model knows what exists and when to reach for one.
|
|
147
191
|
* Returns '' when no skills are installed (so the section is omitted).
|
|
192
|
+
* Skips skills marked disable-model-invocation.
|
|
148
193
|
*/
|
|
149
194
|
export function formatSkillsForPrompt(skills) {
|
|
150
|
-
|
|
195
|
+
const visible = skills.filter(s => !s.disableModelInvocation);
|
|
196
|
+
if (!visible.length)
|
|
151
197
|
return '';
|
|
152
|
-
const lines =
|
|
198
|
+
const lines = visible.map(s => {
|
|
153
199
|
const desc = s.description || '(no description)';
|
|
154
|
-
|
|
200
|
+
const when = s.whenToUse ? ` — when to use: ${s.whenToUse}` : '';
|
|
201
|
+
return `- **${s.name}** — ${desc}${when}`;
|
|
155
202
|
});
|
|
156
203
|
return lines.join('\n');
|
|
157
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Map Claude-style allowed-tools tokens to ikie tool names.
|
|
207
|
+
* Strips arguments, lowercases, and maps known tokens.
|
|
208
|
+
*/
|
|
209
|
+
export function mapAllowedTools(tokens) {
|
|
210
|
+
if (!tokens?.length)
|
|
211
|
+
return [];
|
|
212
|
+
const map = {
|
|
213
|
+
read: 'read_file',
|
|
214
|
+
write: 'write_file',
|
|
215
|
+
edit: 'edit_file',
|
|
216
|
+
bash: 'bash',
|
|
217
|
+
grep: 'grep',
|
|
218
|
+
glob: 'search_files',
|
|
219
|
+
};
|
|
220
|
+
const out = [];
|
|
221
|
+
for (const raw of tokens) {
|
|
222
|
+
const token = raw.replace(/\([^)]*\)/g, '').trim().toLowerCase();
|
|
223
|
+
if (!token)
|
|
224
|
+
continue;
|
|
225
|
+
if (token.startsWith('mcp__')) {
|
|
226
|
+
out.push(token);
|
|
227
|
+
continue;
|
|
228
|
+
}
|
|
229
|
+
const mapped = map[token] ?? token;
|
|
230
|
+
if (!out.includes(mapped))
|
|
231
|
+
out.push(mapped);
|
|
232
|
+
}
|
|
233
|
+
return out;
|
|
234
|
+
}
|
|
158
235
|
/**
|
|
159
236
|
* Build the full text returned to the model when it loads a skill via
|
|
160
237
|
* `use_skill`: the instructions plus a manifest of bundled files and the
|
package/dist/theme.d.ts
CHANGED
|
@@ -80,7 +80,7 @@ export declare function toolMeta(rawName: string): {
|
|
|
80
80
|
};
|
|
81
81
|
export declare function toolLine(name: string, args: string): string;
|
|
82
82
|
/** Multi-line output block shown after a tool runs. */
|
|
83
|
-
export declare function toolOutputBlock(result: string, ms: number, indent?: string): string;
|
|
83
|
+
export declare function toolOutputBlock(result: string, ms: number, indent?: string, savePath?: string): string;
|
|
84
84
|
interface DiffOpts {
|
|
85
85
|
path?: string;
|
|
86
86
|
indent?: string;
|
package/dist/theme.js
CHANGED
|
@@ -397,12 +397,16 @@ export function toolLine(name, args) {
|
|
|
397
397
|
return `${tint('●')} ${c.white.bold(verb + badge)}${c.dim('(')}${c.muted(args)}${c.dim(')')}`;
|
|
398
398
|
}
|
|
399
399
|
/** Multi-line output block shown after a tool runs. */
|
|
400
|
-
export function toolOutputBlock(result, ms, indent = ' ') {
|
|
400
|
+
export function toolOutputBlock(result, ms, indent = ' ', savePath) {
|
|
401
401
|
const time = c.muted(formatDuration(ms));
|
|
402
|
+
// Skip display if output was already streamed
|
|
403
|
+
if (result.includes('__STREAMED__')) {
|
|
404
|
+
return `${indent}${c.muted('⎿')} ${time}`;
|
|
405
|
+
}
|
|
402
406
|
const lines = result.split('\n').filter(l => l.trim() !== '');
|
|
403
407
|
if (!lines.length)
|
|
404
408
|
return `${indent}${c.muted('⎿')} ${time}`;
|
|
405
|
-
const MAX =
|
|
409
|
+
const MAX = 10;
|
|
406
410
|
const shown = lines.slice(0, MAX);
|
|
407
411
|
const hidden = lines.length - MAX;
|
|
408
412
|
const cont = indent + ' ';
|
|
@@ -412,7 +416,13 @@ export function toolOutputBlock(result, ms, indent = ' ') {
|
|
|
412
416
|
out.push(`${cont}${c.dim(clampLine(shown[i]))}`);
|
|
413
417
|
}
|
|
414
418
|
if (hidden > 0) {
|
|
415
|
-
|
|
419
|
+
const outputId = Math.random().toString(36).substring(7);
|
|
420
|
+
const savedOutputs = global;
|
|
421
|
+
if (!savedOutputs._ikieOutputs)
|
|
422
|
+
savedOutputs._ikieOutputs = new Map();
|
|
423
|
+
savedOutputs._ikieOutputs.set(outputId, result);
|
|
424
|
+
out.push(`${cont}${c.muted(`… +${hidden} lines`)} ${c.dim('│')} ${c.info('Full output available')}`);
|
|
425
|
+
out.push(`${cont}${c.muted('└─')} ${c.accent('ikie')} ${c.muted('show-output')} ${c.secondary(outputId)} ${c.dim('# View full output')}`);
|
|
416
426
|
}
|
|
417
427
|
return out.join('\n');
|
|
418
428
|
}
|
|
@@ -528,7 +538,14 @@ export function toolDiffBlock(oldStr, newStr, ms, opts = {}) {
|
|
|
528
538
|
}
|
|
529
539
|
}
|
|
530
540
|
const out = [];
|
|
531
|
-
|
|
541
|
+
// Enhanced header with file path
|
|
542
|
+
if (opts.path) {
|
|
543
|
+
out.push(`${indent}${c.muted('⎿')} ${time} ${c.info('📝')} ${c.white.bold(opts.path)}`);
|
|
544
|
+
out.push(`${indent} ${c.dim(summary)}`);
|
|
545
|
+
}
|
|
546
|
+
else {
|
|
547
|
+
out.push(`${indent}${c.muted('⎿')} ${time} ${c.dim(summary)}`);
|
|
548
|
+
}
|
|
532
549
|
const MAX = 16;
|
|
533
550
|
const codeWidth = cols - indent.length - gw - 4; // gutter + " x " marker + space
|
|
534
551
|
const render = rowsRaw.slice(0, MAX);
|
package/dist/tools.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type OpenAI from 'openai';
|
|
2
2
|
export declare const TOOL_DEFS: OpenAI.Chat.ChatCompletionTool[];
|
|
3
|
+
export declare function getMcpToolDefs(): OpenAI.Chat.ChatCompletionTool[];
|
|
3
4
|
export declare const SAFE_TOOLS: Set<string>;
|
|
4
5
|
export declare const PLAN_TOOLS: Set<string>;
|
|
5
6
|
export declare function isRestrictedPath(path: string): boolean;
|
package/dist/tools.js
CHANGED
|
@@ -3,6 +3,7 @@ import { readFileSync, writeFileSync, existsSync, readdirSync, statSync, mkdirSy
|
|
|
3
3
|
import { dirname, join, resolve } from 'path';
|
|
4
4
|
import { promisify } from 'util';
|
|
5
5
|
import { glob } from 'glob';
|
|
6
|
+
import { getMcpManager, parseClaudeMcpAddCommand } from './mcp-manager.js';
|
|
6
7
|
const execAsync = promisify(exec);
|
|
7
8
|
// ─── OpenAI-format Tool Definitions ──────────────────────────────────────────
|
|
8
9
|
export const TOOL_DEFS = [
|
|
@@ -64,6 +65,7 @@ export const TOOL_DEFS = [
|
|
|
64
65
|
command: { type: 'string', description: 'Shell command' },
|
|
65
66
|
timeout_ms: { type: 'number', description: 'Timeout ms (default 30000)' },
|
|
66
67
|
cwd: { type: 'string', description: 'Working directory' },
|
|
68
|
+
interactive: { type: 'boolean', description: 'Set true for commands that need an interactive terminal — ones that show arrow-key menus or prompts that cannot be skipped with flags (e.g. `shadcn init`, `create-next-app`, `npm init` without -y). The command is connected to the real terminal so the USER answers the prompts directly. Output is shown live, not captured, so the result only reports the exit status — read any files it creates afterward.' },
|
|
67
69
|
},
|
|
68
70
|
required: ['command'],
|
|
69
71
|
},
|
|
@@ -328,28 +330,11 @@ export const TOOL_DEFS = [
|
|
|
328
330
|
},
|
|
329
331
|
},
|
|
330
332
|
},
|
|
331
|
-
{
|
|
332
|
-
type: 'function',
|
|
333
|
-
function: {
|
|
334
|
-
name: 'mcp_install',
|
|
335
|
-
description: 'Install a new MCP (Model Context Protocol) server to extend capabilities. MCPs provide specialized tools like GitHub API, database access, browser automation, etc.',
|
|
336
|
-
parameters: {
|
|
337
|
-
type: 'object',
|
|
338
|
-
properties: {
|
|
339
|
-
name: { type: 'string', description: 'Unique name for this MCP' },
|
|
340
|
-
source: { type: 'string', description: 'Source: npm package (npm:package-name), git URL, or local path' },
|
|
341
|
-
description: { type: 'string', description: 'Description of what this MCP does' },
|
|
342
|
-
autoStart: { type: 'boolean', description: 'Start automatically on ikie launch (default: false)' },
|
|
343
|
-
},
|
|
344
|
-
required: ['name', 'source'],
|
|
345
|
-
},
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
333
|
{
|
|
349
334
|
type: 'function',
|
|
350
335
|
function: {
|
|
351
336
|
name: 'mcp_list',
|
|
352
|
-
description: 'List all
|
|
337
|
+
description: 'List all configured MCP servers and their available tools.',
|
|
353
338
|
parameters: {
|
|
354
339
|
type: 'object',
|
|
355
340
|
properties: {},
|
|
@@ -357,50 +342,6 @@ export const TOOL_DEFS = [
|
|
|
357
342
|
},
|
|
358
343
|
},
|
|
359
344
|
},
|
|
360
|
-
{
|
|
361
|
-
type: 'function',
|
|
362
|
-
function: {
|
|
363
|
-
name: 'mcp_start',
|
|
364
|
-
description: 'Start an installed MCP server to make its tools available.',
|
|
365
|
-
parameters: {
|
|
366
|
-
type: 'object',
|
|
367
|
-
properties: {
|
|
368
|
-
name: { type: 'string', description: 'Name of the MCP server to start' },
|
|
369
|
-
},
|
|
370
|
-
required: ['name'],
|
|
371
|
-
},
|
|
372
|
-
},
|
|
373
|
-
},
|
|
374
|
-
{
|
|
375
|
-
type: 'function',
|
|
376
|
-
function: {
|
|
377
|
-
name: 'mcp_stop',
|
|
378
|
-
description: 'Stop a running MCP server.',
|
|
379
|
-
parameters: {
|
|
380
|
-
type: 'object',
|
|
381
|
-
properties: {
|
|
382
|
-
name: { type: 'string', description: 'Name of the MCP server to stop' },
|
|
383
|
-
},
|
|
384
|
-
required: ['name'],
|
|
385
|
-
},
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
{
|
|
389
|
-
type: 'function',
|
|
390
|
-
function: {
|
|
391
|
-
name: 'mcp_call',
|
|
392
|
-
description: 'Call a tool from a running MCP server. Use mcp_list first to see available tools and their parameters.',
|
|
393
|
-
parameters: {
|
|
394
|
-
type: 'object',
|
|
395
|
-
properties: {
|
|
396
|
-
server: { type: 'string', description: 'MCP server name' },
|
|
397
|
-
tool: { type: 'string', description: 'Tool name from the MCP server' },
|
|
398
|
-
arguments: { type: 'object', description: 'Arguments to pass to the tool' },
|
|
399
|
-
},
|
|
400
|
-
required: ['server', 'tool', 'arguments'],
|
|
401
|
-
},
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
345
|
{
|
|
405
346
|
type: 'function',
|
|
406
347
|
function: {
|
|
@@ -418,29 +359,20 @@ export const TOOL_DEFS = [
|
|
|
418
359
|
},
|
|
419
360
|
},
|
|
420
361
|
},
|
|
421
|
-
{
|
|
422
|
-
type: 'function',
|
|
423
|
-
function: {
|
|
424
|
-
name: 'mcp_uninstall',
|
|
425
|
-
description: 'Uninstall an MCP server (cannot uninstall built-in MCPs).',
|
|
426
|
-
parameters: {
|
|
427
|
-
type: 'object',
|
|
428
|
-
properties: {
|
|
429
|
-
name: { type: 'string', description: 'Name of the MCP server to uninstall' },
|
|
430
|
-
},
|
|
431
|
-
required: ['name'],
|
|
432
|
-
},
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
362
|
];
|
|
363
|
+
// ─── MCP first-class tool definitions ─────────────────────────────────────────
|
|
364
|
+
export function getMcpToolDefs() {
|
|
365
|
+
return getMcpManager().getToolDefsSync();
|
|
366
|
+
}
|
|
436
367
|
// ─── Safe tools (auto-approved) ───────────────────────────────────────────────
|
|
437
368
|
// spawn_agent is "safe" at the dispatch layer — the tools the sub-agent itself
|
|
438
369
|
// runs go through their own approval inside the sub-agent loop.
|
|
439
|
-
export const SAFE_TOOLS = new Set(['read_file', 'list_dir', 'search_files', 'grep', 'memory_write', 'spawn_agent', 'ask_user', 'fetch_url', 'web_search', 'git_status', 'git_diff', 'git_log', 'git_branch', 'use_skill', 'install_skill', 'remove_skill', 'mcp_list'
|
|
370
|
+
export const SAFE_TOOLS = new Set(['read_file', 'list_dir', 'search_files', 'grep', 'memory_write', 'spawn_agent', 'ask_user', 'fetch_url', 'web_search', 'git_status', 'git_diff', 'git_log', 'git_branch', 'use_skill', 'install_skill', 'remove_skill', 'mcp_list']);
|
|
440
371
|
// Tools available in PLAN mode — read-only exploration plus delegation/questions.
|
|
441
372
|
// Everything that mutates the filesystem or runs commands (write_file, edit_file,
|
|
442
373
|
// bash, memory_write) is intentionally excluded so plan mode can only research.
|
|
443
|
-
|
|
374
|
+
// MCP tools are also excluded because we cannot prove they are read-only.
|
|
375
|
+
export const PLAN_TOOLS = new Set(['read_file', 'list_dir', 'search_files', 'grep', 'spawn_agent', 'ask_user', 'fetch_url', 'web_search', 'git_status', 'git_diff', 'git_log', 'git_branch', 'use_skill', 'install_skill', 'remove_skill', 'mcp_list']);
|
|
444
376
|
// Paths that may contain secrets, credentials, or system configuration.
|
|
445
377
|
// Reading these requires explicit user permission even though read_file is normally safe.
|
|
446
378
|
const RESTRICTED_PATTERNS = [
|
|
@@ -524,8 +456,17 @@ export function formatToolArgs(name, input) {
|
|
|
524
456
|
return `"${p(input.source)}"${input.force ? ' --force' : ''}`;
|
|
525
457
|
case 'remove_skill':
|
|
526
458
|
return input.name ? `"${p(input.name)}"` : '(list installed)';
|
|
527
|
-
|
|
459
|
+
case 'mcp_list':
|
|
460
|
+
return '(status)';
|
|
461
|
+
case 'mcp_add':
|
|
462
|
+
return `"${p(input.name)}"`;
|
|
463
|
+
default: {
|
|
464
|
+
if (name.startsWith('mcp__')) {
|
|
465
|
+
const parts = name.split('__');
|
|
466
|
+
return `${parts[1] ?? '?'}.${parts[2] ?? '?'}`;
|
|
467
|
+
}
|
|
528
468
|
return JSON.stringify(input).slice(0, 80);
|
|
469
|
+
}
|
|
529
470
|
}
|
|
530
471
|
}
|
|
531
472
|
// ─── Security Validation Functions ───────────────────────────────────────────
|
|
@@ -623,6 +564,9 @@ async function bash(input) {
|
|
|
623
564
|
cwd = resolve(input.cwd);
|
|
624
565
|
}
|
|
625
566
|
const command = input.command.trim();
|
|
567
|
+
if (input.interactive) {
|
|
568
|
+
return bashInteractive(command, cwd);
|
|
569
|
+
}
|
|
626
570
|
if (command.endsWith('&')) {
|
|
627
571
|
const bgCmd = command.slice(0, -1).trim();
|
|
628
572
|
try {
|
|
@@ -645,6 +589,11 @@ async function bash(input) {
|
|
|
645
589
|
}
|
|
646
590
|
const maxTimeout = 300000;
|
|
647
591
|
const timeout = Math.min(input.timeout_ms ?? 60000, maxTimeout);
|
|
592
|
+
// For build commands or when stream is enabled, use streaming output
|
|
593
|
+
const shouldStream = input.stream || /\b(build|compile|test|deploy|install)\b/i.test(command);
|
|
594
|
+
if (shouldStream) {
|
|
595
|
+
return bashStreaming(command, cwd, timeout);
|
|
596
|
+
}
|
|
648
597
|
try {
|
|
649
598
|
const { stdout, stderr } = await execAsync(command, {
|
|
650
599
|
cwd,
|
|
@@ -670,6 +619,126 @@ async function bash(input) {
|
|
|
670
619
|
return `Exit ${e.code ?? 1}\n${parts.join('\n')}`;
|
|
671
620
|
}
|
|
672
621
|
}
|
|
622
|
+
function bashStreaming(command, cwd, timeout) {
|
|
623
|
+
return new Promise((resolve, reject) => {
|
|
624
|
+
const isWindows = process.platform === 'win32';
|
|
625
|
+
const child = spawn(isWindows ? 'cmd.exe' : 'bash', isWindows ? ['/d', '/s', '/c', command] : ['-c', command], { cwd, timeout });
|
|
626
|
+
let stdout = '';
|
|
627
|
+
let stderr = '';
|
|
628
|
+
const indent = ' ';
|
|
629
|
+
const MAX_LINES_SHOWN = 3;
|
|
630
|
+
let linesShown = 0;
|
|
631
|
+
let totalLines = 0;
|
|
632
|
+
// Print a blank line before streaming output
|
|
633
|
+
process.stdout.write('\n');
|
|
634
|
+
// Stream stdout line by line - show only first few lines
|
|
635
|
+
child.stdout?.on('data', (data) => {
|
|
636
|
+
const text = data.toString();
|
|
637
|
+
stdout += text;
|
|
638
|
+
const lines = text.split('\n').filter(l => l.trim());
|
|
639
|
+
for (const line of lines) {
|
|
640
|
+
totalLines++;
|
|
641
|
+
if (linesShown < MAX_LINES_SHOWN) {
|
|
642
|
+
process.stdout.write(`${indent}${line}\n`);
|
|
643
|
+
linesShown++;
|
|
644
|
+
}
|
|
645
|
+
}
|
|
646
|
+
});
|
|
647
|
+
child.stderr?.on('data', (data) => {
|
|
648
|
+
const text = data.toString();
|
|
649
|
+
stderr += text;
|
|
650
|
+
const lines = text.split('\n').filter(l => l.trim());
|
|
651
|
+
for (const line of lines) {
|
|
652
|
+
totalLines++;
|
|
653
|
+
if (linesShown < MAX_LINES_SHOWN) {
|
|
654
|
+
process.stdout.write(`${indent}${line}\n`);
|
|
655
|
+
linesShown++;
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
});
|
|
659
|
+
child.on('error', (err) => {
|
|
660
|
+
reject(new Error(`Failed to execute command: ${sanitizeError(err)}`));
|
|
661
|
+
});
|
|
662
|
+
child.on('exit', (code) => {
|
|
663
|
+
// Show truncation message if there are hidden lines
|
|
664
|
+
const hiddenLines = totalLines - linesShown;
|
|
665
|
+
if (hiddenLines > 0) {
|
|
666
|
+
process.stdout.write(`${indent}... +${hiddenLines} lines\n`);
|
|
667
|
+
}
|
|
668
|
+
const parts = [];
|
|
669
|
+
if (stdout.trim())
|
|
670
|
+
parts.push(stdout.trim());
|
|
671
|
+
if (stderr.trim())
|
|
672
|
+
parts.push(`[stderr]\n${stderr.trim()}`);
|
|
673
|
+
// Return special marker to prevent re-display
|
|
674
|
+
const output = parts.join('\n') || '(no output)';
|
|
675
|
+
if (code !== 0) {
|
|
676
|
+
resolve(`Exit ${code}\n__STREAMED__\n${output}`);
|
|
677
|
+
}
|
|
678
|
+
else {
|
|
679
|
+
resolve(`__STREAMED__\n${output}`);
|
|
680
|
+
}
|
|
681
|
+
});
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
/**
|
|
685
|
+
* Runs a command attached to the real terminal (stdio: 'inherit') so the USER
|
|
686
|
+
* can answer interactive prompts (arrow-key menus, y/N questions) that the
|
|
687
|
+
* non-interactive path cannot handle. Temporarily detaches the REPL's own stdin
|
|
688
|
+
* listeners and raw mode while the child owns the terminal, then restores them.
|
|
689
|
+
* Output is not captured — only the exit status is reported back to the agent.
|
|
690
|
+
*/
|
|
691
|
+
function bashInteractive(command, cwd) {
|
|
692
|
+
return new Promise((resolvePromise) => {
|
|
693
|
+
const stdin = process.stdin;
|
|
694
|
+
const isTTY = Boolean(stdin.isTTY);
|
|
695
|
+
const wasRaw = isTTY ? Boolean(stdin.isRaw) : false;
|
|
696
|
+
// Save and detach whatever the REPL has on stdin (cancel handler, etc.) so
|
|
697
|
+
// the child receives keystrokes directly.
|
|
698
|
+
const saved = isTTY ? stdin.rawListeners('data').slice() : [];
|
|
699
|
+
for (const l of saved)
|
|
700
|
+
stdin.removeListener('data', l);
|
|
701
|
+
const restore = () => {
|
|
702
|
+
if (isTTY) {
|
|
703
|
+
try {
|
|
704
|
+
stdin.setRawMode(wasRaw);
|
|
705
|
+
}
|
|
706
|
+
catch { /* ignore */ }
|
|
707
|
+
if (process.stdout.isTTY)
|
|
708
|
+
process.stdout.write('\x1b[?2004h'); // re-arm bracketed paste
|
|
709
|
+
}
|
|
710
|
+
for (const l of saved)
|
|
711
|
+
stdin.on('data', l);
|
|
712
|
+
};
|
|
713
|
+
if (isTTY) {
|
|
714
|
+
try {
|
|
715
|
+
stdin.setRawMode(false);
|
|
716
|
+
}
|
|
717
|
+
catch { /* ignore */ }
|
|
718
|
+
if (process.stdout.isTTY)
|
|
719
|
+
process.stdout.write('\x1b[?2004l'); // disable bracketed paste for the child
|
|
720
|
+
}
|
|
721
|
+
process.stdout.write('\n');
|
|
722
|
+
try {
|
|
723
|
+
const isWindows = process.platform === 'win32';
|
|
724
|
+
const child = spawn(isWindows ? 'cmd.exe' : 'bash', isWindows ? ['/d', '/s', '/c', command] : ['-c', command], { cwd, stdio: 'inherit' });
|
|
725
|
+
child.on('error', (err) => {
|
|
726
|
+
restore();
|
|
727
|
+
resolvePromise(`Error running interactive command: ${sanitizeError(err)}`);
|
|
728
|
+
});
|
|
729
|
+
child.on('exit', (code) => {
|
|
730
|
+
restore();
|
|
731
|
+
resolvePromise(code === 0
|
|
732
|
+
? '(interactive command completed — output was shown to the user; read any created/changed files to see the result)'
|
|
733
|
+
: `Exit ${code ?? 1} (interactive command)`);
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
catch (err) {
|
|
737
|
+
restore();
|
|
738
|
+
resolvePromise(`Error running interactive command: ${sanitizeError(err)}`);
|
|
739
|
+
}
|
|
740
|
+
});
|
|
741
|
+
}
|
|
673
742
|
function listDir(input) {
|
|
674
743
|
const root = resolve(input.path ?? '.');
|
|
675
744
|
if (!existsSync(root))
|
|
@@ -1065,98 +1134,40 @@ async function removeSkill(input) {
|
|
|
1065
1134
|
return removed ? `Removed skill "${name}".` : `No skill named "${name}" found.`;
|
|
1066
1135
|
}
|
|
1067
1136
|
// ─── MCP Functions ────────────────────────────────────────────────────────────
|
|
1068
|
-
async function mcpInstall(input) {
|
|
1069
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1070
|
-
const manager = getMCPManager();
|
|
1071
|
-
const result = await manager.installMCP({
|
|
1072
|
-
name: input.name,
|
|
1073
|
-
source: input.source,
|
|
1074
|
-
description: input.description,
|
|
1075
|
-
autoStart: input.autoStart,
|
|
1076
|
-
});
|
|
1077
|
-
if (!result.success) {
|
|
1078
|
-
return `Error installing MCP: ${result.error}`;
|
|
1079
|
-
}
|
|
1080
|
-
return `✓ Installed MCP "${input.name}".\nRun mcp_start to activate it.`;
|
|
1081
|
-
}
|
|
1082
1137
|
async function mcpList() {
|
|
1083
|
-
const
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
if (!mcps.length) {
|
|
1087
|
-
return 'No MCPs installed. Use mcp_install to add MCP servers.';
|
|
1138
|
+
const servers = getMcpManager().listServers();
|
|
1139
|
+
if (!servers.length) {
|
|
1140
|
+
return 'No MCP servers configured. Use mcp_add or create a .mcp.json file.';
|
|
1088
1141
|
}
|
|
1089
|
-
const lines = ['
|
|
1090
|
-
for (const
|
|
1091
|
-
const status =
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
if (
|
|
1096
|
-
lines.push(`
|
|
1142
|
+
const lines = ['MCP servers:\n'];
|
|
1143
|
+
for (const s of servers) {
|
|
1144
|
+
const status = s.connected ? '🟢 connected' : s.enabled ? '⚪ disabled/not connected' : '⚫ disabled';
|
|
1145
|
+
lines.push(`${status} ${s.name}`);
|
|
1146
|
+
if (s.error)
|
|
1147
|
+
lines.push(` error: ${s.error}`);
|
|
1148
|
+
if (s.tools.length) {
|
|
1149
|
+
lines.push(` tools: ${s.tools.map(t => t.name).join(', ')}`);
|
|
1097
1150
|
}
|
|
1098
1151
|
lines.push('');
|
|
1099
1152
|
}
|
|
1100
1153
|
return lines.join('\n');
|
|
1101
1154
|
}
|
|
1102
|
-
async function
|
|
1103
|
-
const
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
if (!result.success) {
|
|
1107
|
-
return `Error starting MCP: ${result.error}`;
|
|
1108
|
-
}
|
|
1109
|
-
const toolCount = result.tools?.length || 0;
|
|
1110
|
-
const toolNames = result.tools?.map(t => t.name).join(', ') || 'none';
|
|
1111
|
-
return `✓ Started MCP "${input.name}".\nAvailable tools (${toolCount}): ${toolNames}`;
|
|
1112
|
-
}
|
|
1113
|
-
async function mcpStop(input) {
|
|
1114
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1115
|
-
const manager = getMCPManager();
|
|
1116
|
-
const result = manager.stopMCP(input.name);
|
|
1117
|
-
if (!result.success) {
|
|
1118
|
-
return `Error stopping MCP: ${result.error}`;
|
|
1155
|
+
async function mcpAdd(input) {
|
|
1156
|
+
const parsed = parseClaudeMcpAddCommand(`mcp add ${input.name} ${input.commandArgs}`);
|
|
1157
|
+
if ('error' in parsed) {
|
|
1158
|
+
return `Error adding MCP: ${parsed.error}`;
|
|
1119
1159
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
async function mcpCall(input) {
|
|
1123
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1124
|
-
const manager = getMCPManager();
|
|
1125
|
-
const result = await manager.callMCPTool(input.server, input.tool, input.arguments);
|
|
1126
|
-
if (!result.success) {
|
|
1127
|
-
return `Error calling MCP tool: ${result.error}`;
|
|
1160
|
+
if (parsed.name !== input.name) {
|
|
1161
|
+
return `Error adding MCP: name mismatch`;
|
|
1128
1162
|
}
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
}
|
|
1133
|
-
async function mcpUninstall(input) {
|
|
1134
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1135
|
-
const manager = getMCPManager();
|
|
1136
|
-
const result = manager.uninstallMCP(input.name);
|
|
1137
|
-
if (!result.success) {
|
|
1138
|
-
return `Error uninstalling MCP: ${result.error}`;
|
|
1163
|
+
const entry = { ...parsed.entry, description: input.description, env: input.env ?? parsed.entry.env };
|
|
1164
|
+
try {
|
|
1165
|
+
await getMcpManager().addServer(input.name, entry, 'user');
|
|
1166
|
+
return `✓ Added MCP "${input.name}" and connected.\n Command: ${input.commandArgs}`;
|
|
1139
1167
|
}
|
|
1140
|
-
|
|
1141
|
-
}
|
|
1142
|
-
async function mcpAdd(input) {
|
|
1143
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1144
|
-
const manager = getMCPManager();
|
|
1145
|
-
const parts = input.commandArgs.trim().split(/\s+/);
|
|
1146
|
-
const cmd = parts[0];
|
|
1147
|
-
const args = parts.slice(1);
|
|
1148
|
-
const result = manager.addMCP({
|
|
1149
|
-
name: input.name,
|
|
1150
|
-
command: cmd,
|
|
1151
|
-
args,
|
|
1152
|
-
env: input.env,
|
|
1153
|
-
description: input.description,
|
|
1154
|
-
});
|
|
1155
|
-
if (!result.success) {
|
|
1156
|
-
return `Error adding MCP: ${result.error}`;
|
|
1168
|
+
catch (err) {
|
|
1169
|
+
return `Error adding MCP: ${err instanceof Error ? err.message : String(err)}`;
|
|
1157
1170
|
}
|
|
1158
|
-
const envStr = input.env ? `\n Env: ${Object.keys(input.env).join(', ')}` : '';
|
|
1159
|
-
return `✓ Added MCP "${input.name}".\n Command: ${input.commandArgs}${envStr}\n Run mcp_start to activate it.`;
|
|
1160
1171
|
}
|
|
1161
1172
|
// ─── Dispatcher ───────────────────────────────────────────────────────────────
|
|
1162
1173
|
export async function executeTool(name, input) {
|
|
@@ -1180,13 +1191,13 @@ export async function executeTool(name, input) {
|
|
|
1180
1191
|
case 'use_skill': return useSkill(input);
|
|
1181
1192
|
case 'install_skill': return installSkill(input);
|
|
1182
1193
|
case 'remove_skill': return removeSkill(input);
|
|
1183
|
-
case 'mcp_install': return mcpInstall(input);
|
|
1184
1194
|
case 'mcp_list': return mcpList();
|
|
1185
|
-
case 'mcp_start': return mcpStart(input);
|
|
1186
|
-
case 'mcp_stop': return mcpStop(input);
|
|
1187
|
-
case 'mcp_call': return mcpCall(input);
|
|
1188
|
-
case 'mcp_uninstall': return mcpUninstall(input);
|
|
1189
1195
|
case 'mcp_add': return mcpAdd(input);
|
|
1190
|
-
default:
|
|
1196
|
+
default: {
|
|
1197
|
+
if (name.startsWith('mcp__')) {
|
|
1198
|
+
return getMcpManager().callTool(name, input);
|
|
1199
|
+
}
|
|
1200
|
+
return `Unknown tool: ${name}`;
|
|
1201
|
+
}
|
|
1191
1202
|
}
|
|
1192
1203
|
}
|
package/dist/tree.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
interface TreeOptions {
|
|
2
|
+
maxDepth?: number;
|
|
3
|
+
showHidden?: boolean;
|
|
4
|
+
showSize?: boolean;
|
|
5
|
+
exclude?: string[];
|
|
6
|
+
currentDepth?: number;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Generate a visual file tree
|
|
10
|
+
*/
|
|
11
|
+
export declare function generateTree(rootPath: string, options?: TreeOptions): string;
|
|
12
|
+
/**
|
|
13
|
+
* Parse tree command arguments
|
|
14
|
+
*/
|
|
15
|
+
export declare function parseTreeArgs(args: string[]): {
|
|
16
|
+
path: string;
|
|
17
|
+
options: TreeOptions;
|
|
18
|
+
};
|
|
19
|
+
export {};
|