ikie-cli 0.1.33 → 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 +26 -9
- package/dist/agent.d.ts +44 -0
- package/dist/agent.js +372 -120
- 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 +297 -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 +115 -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 = [
|
|
@@ -329,28 +330,11 @@ export const TOOL_DEFS = [
|
|
|
329
330
|
},
|
|
330
331
|
},
|
|
331
332
|
},
|
|
332
|
-
{
|
|
333
|
-
type: 'function',
|
|
334
|
-
function: {
|
|
335
|
-
name: 'mcp_install',
|
|
336
|
-
description: 'Install a new MCP (Model Context Protocol) server to extend capabilities. MCPs provide specialized tools like GitHub API, database access, browser automation, etc.',
|
|
337
|
-
parameters: {
|
|
338
|
-
type: 'object',
|
|
339
|
-
properties: {
|
|
340
|
-
name: { type: 'string', description: 'Unique name for this MCP' },
|
|
341
|
-
source: { type: 'string', description: 'Source: npm package (npm:package-name), git URL, or local path' },
|
|
342
|
-
description: { type: 'string', description: 'Description of what this MCP does' },
|
|
343
|
-
autoStart: { type: 'boolean', description: 'Start automatically on ikie launch (default: false)' },
|
|
344
|
-
},
|
|
345
|
-
required: ['name', 'source'],
|
|
346
|
-
},
|
|
347
|
-
},
|
|
348
|
-
},
|
|
349
333
|
{
|
|
350
334
|
type: 'function',
|
|
351
335
|
function: {
|
|
352
336
|
name: 'mcp_list',
|
|
353
|
-
description: 'List all
|
|
337
|
+
description: 'List all configured MCP servers and their available tools.',
|
|
354
338
|
parameters: {
|
|
355
339
|
type: 'object',
|
|
356
340
|
properties: {},
|
|
@@ -358,50 +342,6 @@ export const TOOL_DEFS = [
|
|
|
358
342
|
},
|
|
359
343
|
},
|
|
360
344
|
},
|
|
361
|
-
{
|
|
362
|
-
type: 'function',
|
|
363
|
-
function: {
|
|
364
|
-
name: 'mcp_start',
|
|
365
|
-
description: 'Start an installed MCP server to make its tools available.',
|
|
366
|
-
parameters: {
|
|
367
|
-
type: 'object',
|
|
368
|
-
properties: {
|
|
369
|
-
name: { type: 'string', description: 'Name of the MCP server to start' },
|
|
370
|
-
},
|
|
371
|
-
required: ['name'],
|
|
372
|
-
},
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
{
|
|
376
|
-
type: 'function',
|
|
377
|
-
function: {
|
|
378
|
-
name: 'mcp_stop',
|
|
379
|
-
description: 'Stop a running MCP server.',
|
|
380
|
-
parameters: {
|
|
381
|
-
type: 'object',
|
|
382
|
-
properties: {
|
|
383
|
-
name: { type: 'string', description: 'Name of the MCP server to stop' },
|
|
384
|
-
},
|
|
385
|
-
required: ['name'],
|
|
386
|
-
},
|
|
387
|
-
},
|
|
388
|
-
},
|
|
389
|
-
{
|
|
390
|
-
type: 'function',
|
|
391
|
-
function: {
|
|
392
|
-
name: 'mcp_call',
|
|
393
|
-
description: 'Call a tool from a running MCP server. Use mcp_list first to see available tools and their parameters.',
|
|
394
|
-
parameters: {
|
|
395
|
-
type: 'object',
|
|
396
|
-
properties: {
|
|
397
|
-
server: { type: 'string', description: 'MCP server name' },
|
|
398
|
-
tool: { type: 'string', description: 'Tool name from the MCP server' },
|
|
399
|
-
arguments: { type: 'object', description: 'Arguments to pass to the tool' },
|
|
400
|
-
},
|
|
401
|
-
required: ['server', 'tool', 'arguments'],
|
|
402
|
-
},
|
|
403
|
-
},
|
|
404
|
-
},
|
|
405
345
|
{
|
|
406
346
|
type: 'function',
|
|
407
347
|
function: {
|
|
@@ -419,29 +359,20 @@ export const TOOL_DEFS = [
|
|
|
419
359
|
},
|
|
420
360
|
},
|
|
421
361
|
},
|
|
422
|
-
{
|
|
423
|
-
type: 'function',
|
|
424
|
-
function: {
|
|
425
|
-
name: 'mcp_uninstall',
|
|
426
|
-
description: 'Uninstall an MCP server (cannot uninstall built-in MCPs).',
|
|
427
|
-
parameters: {
|
|
428
|
-
type: 'object',
|
|
429
|
-
properties: {
|
|
430
|
-
name: { type: 'string', description: 'Name of the MCP server to uninstall' },
|
|
431
|
-
},
|
|
432
|
-
required: ['name'],
|
|
433
|
-
},
|
|
434
|
-
},
|
|
435
|
-
},
|
|
436
362
|
];
|
|
363
|
+
// ─── MCP first-class tool definitions ─────────────────────────────────────────
|
|
364
|
+
export function getMcpToolDefs() {
|
|
365
|
+
return getMcpManager().getToolDefsSync();
|
|
366
|
+
}
|
|
437
367
|
// ─── Safe tools (auto-approved) ───────────────────────────────────────────────
|
|
438
368
|
// spawn_agent is "safe" at the dispatch layer — the tools the sub-agent itself
|
|
439
369
|
// runs go through their own approval inside the sub-agent loop.
|
|
440
|
-
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']);
|
|
441
371
|
// Tools available in PLAN mode — read-only exploration plus delegation/questions.
|
|
442
372
|
// Everything that mutates the filesystem or runs commands (write_file, edit_file,
|
|
443
373
|
// bash, memory_write) is intentionally excluded so plan mode can only research.
|
|
444
|
-
|
|
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']);
|
|
445
376
|
// Paths that may contain secrets, credentials, or system configuration.
|
|
446
377
|
// Reading these requires explicit user permission even though read_file is normally safe.
|
|
447
378
|
const RESTRICTED_PATTERNS = [
|
|
@@ -525,8 +456,17 @@ export function formatToolArgs(name, input) {
|
|
|
525
456
|
return `"${p(input.source)}"${input.force ? ' --force' : ''}`;
|
|
526
457
|
case 'remove_skill':
|
|
527
458
|
return input.name ? `"${p(input.name)}"` : '(list installed)';
|
|
528
|
-
|
|
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
|
+
}
|
|
529
468
|
return JSON.stringify(input).slice(0, 80);
|
|
469
|
+
}
|
|
530
470
|
}
|
|
531
471
|
}
|
|
532
472
|
// ─── Security Validation Functions ───────────────────────────────────────────
|
|
@@ -649,6 +589,11 @@ async function bash(input) {
|
|
|
649
589
|
}
|
|
650
590
|
const maxTimeout = 300000;
|
|
651
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
|
+
}
|
|
652
597
|
try {
|
|
653
598
|
const { stdout, stderr } = await execAsync(command, {
|
|
654
599
|
cwd,
|
|
@@ -674,6 +619,68 @@ async function bash(input) {
|
|
|
674
619
|
return `Exit ${e.code ?? 1}\n${parts.join('\n')}`;
|
|
675
620
|
}
|
|
676
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
|
+
}
|
|
677
684
|
/**
|
|
678
685
|
* Runs a command attached to the real terminal (stdio: 'inherit') so the USER
|
|
679
686
|
* can answer interactive prompts (arrow-key menus, y/N questions) that the
|
|
@@ -1127,98 +1134,40 @@ async function removeSkill(input) {
|
|
|
1127
1134
|
return removed ? `Removed skill "${name}".` : `No skill named "${name}" found.`;
|
|
1128
1135
|
}
|
|
1129
1136
|
// ─── MCP Functions ────────────────────────────────────────────────────────────
|
|
1130
|
-
async function mcpInstall(input) {
|
|
1131
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1132
|
-
const manager = getMCPManager();
|
|
1133
|
-
const result = await manager.installMCP({
|
|
1134
|
-
name: input.name,
|
|
1135
|
-
source: input.source,
|
|
1136
|
-
description: input.description,
|
|
1137
|
-
autoStart: input.autoStart,
|
|
1138
|
-
});
|
|
1139
|
-
if (!result.success) {
|
|
1140
|
-
return `Error installing MCP: ${result.error}`;
|
|
1141
|
-
}
|
|
1142
|
-
return `✓ Installed MCP "${input.name}".\nRun mcp_start to activate it.`;
|
|
1143
|
-
}
|
|
1144
1137
|
async function mcpList() {
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
if (!mcps.length) {
|
|
1149
|
-
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.';
|
|
1150
1141
|
}
|
|
1151
|
-
const lines = ['
|
|
1152
|
-
for (const
|
|
1153
|
-
const status =
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
if (
|
|
1158
|
-
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(', ')}`);
|
|
1159
1150
|
}
|
|
1160
1151
|
lines.push('');
|
|
1161
1152
|
}
|
|
1162
1153
|
return lines.join('\n');
|
|
1163
1154
|
}
|
|
1164
|
-
async function
|
|
1165
|
-
const
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
if (!result.success) {
|
|
1169
|
-
return `Error starting MCP: ${result.error}`;
|
|
1170
|
-
}
|
|
1171
|
-
const toolCount = result.tools?.length || 0;
|
|
1172
|
-
const toolNames = result.tools?.map(t => t.name).join(', ') || 'none';
|
|
1173
|
-
return `✓ Started MCP "${input.name}".\nAvailable tools (${toolCount}): ${toolNames}`;
|
|
1174
|
-
}
|
|
1175
|
-
async function mcpStop(input) {
|
|
1176
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1177
|
-
const manager = getMCPManager();
|
|
1178
|
-
const result = manager.stopMCP(input.name);
|
|
1179
|
-
if (!result.success) {
|
|
1180
|
-
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}`;
|
|
1181
1159
|
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
async function mcpCall(input) {
|
|
1185
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1186
|
-
const manager = getMCPManager();
|
|
1187
|
-
const result = await manager.callMCPTool(input.server, input.tool, input.arguments);
|
|
1188
|
-
if (!result.success) {
|
|
1189
|
-
return `Error calling MCP tool: ${result.error}`;
|
|
1160
|
+
if (parsed.name !== input.name) {
|
|
1161
|
+
return `Error adding MCP: name mismatch`;
|
|
1190
1162
|
}
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
}
|
|
1195
|
-
async function mcpUninstall(input) {
|
|
1196
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1197
|
-
const manager = getMCPManager();
|
|
1198
|
-
const result = manager.uninstallMCP(input.name);
|
|
1199
|
-
if (!result.success) {
|
|
1200
|
-
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}`;
|
|
1201
1167
|
}
|
|
1202
|
-
|
|
1203
|
-
}
|
|
1204
|
-
async function mcpAdd(input) {
|
|
1205
|
-
const { getMCPManager } = await import('./mcp-manager.js');
|
|
1206
|
-
const manager = getMCPManager();
|
|
1207
|
-
const parts = input.commandArgs.trim().split(/\s+/);
|
|
1208
|
-
const cmd = parts[0];
|
|
1209
|
-
const args = parts.slice(1);
|
|
1210
|
-
const result = manager.addMCP({
|
|
1211
|
-
name: input.name,
|
|
1212
|
-
command: cmd,
|
|
1213
|
-
args,
|
|
1214
|
-
env: input.env,
|
|
1215
|
-
description: input.description,
|
|
1216
|
-
});
|
|
1217
|
-
if (!result.success) {
|
|
1218
|
-
return `Error adding MCP: ${result.error}`;
|
|
1168
|
+
catch (err) {
|
|
1169
|
+
return `Error adding MCP: ${err instanceof Error ? err.message : String(err)}`;
|
|
1219
1170
|
}
|
|
1220
|
-
const envStr = input.env ? `\n Env: ${Object.keys(input.env).join(', ')}` : '';
|
|
1221
|
-
return `✓ Added MCP "${input.name}".\n Command: ${input.commandArgs}${envStr}\n Run mcp_start to activate it.`;
|
|
1222
1171
|
}
|
|
1223
1172
|
// ─── Dispatcher ───────────────────────────────────────────────────────────────
|
|
1224
1173
|
export async function executeTool(name, input) {
|
|
@@ -1242,13 +1191,13 @@ export async function executeTool(name, input) {
|
|
|
1242
1191
|
case 'use_skill': return useSkill(input);
|
|
1243
1192
|
case 'install_skill': return installSkill(input);
|
|
1244
1193
|
case 'remove_skill': return removeSkill(input);
|
|
1245
|
-
case 'mcp_install': return mcpInstall(input);
|
|
1246
1194
|
case 'mcp_list': return mcpList();
|
|
1247
|
-
case 'mcp_start': return mcpStart(input);
|
|
1248
|
-
case 'mcp_stop': return mcpStop(input);
|
|
1249
|
-
case 'mcp_call': return mcpCall(input);
|
|
1250
|
-
case 'mcp_uninstall': return mcpUninstall(input);
|
|
1251
1195
|
case 'mcp_add': return mcpAdd(input);
|
|
1252
|
-
default:
|
|
1196
|
+
default: {
|
|
1197
|
+
if (name.startsWith('mcp__')) {
|
|
1198
|
+
return getMcpManager().callTool(name, input);
|
|
1199
|
+
}
|
|
1200
|
+
return `Unknown tool: ${name}`;
|
|
1201
|
+
}
|
|
1253
1202
|
}
|
|
1254
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 {};
|