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/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
- for (const line of m[1].split(/\r?\n/)) {
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[kv[1].toLowerCase()] = value;
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
- if (!skills.length)
195
+ const visible = skills.filter(s => !s.disableModelInvocation);
196
+ if (!visible.length)
151
197
  return '';
152
- const lines = skills.map(s => {
198
+ const lines = visible.map(s => {
153
199
  const desc = s.description || '(no description)';
154
- return `- **${s.name}** — ${desc}`;
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 = 5;
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
- out.push(`${cont}${c.muted(`… +${hidden} lines`)}`);
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
- out.push(`${indent}${c.muted('⎿')} ${time} ${c.dim(summary)}`);
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 installed MCP servers and their available tools.',
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', 'mcp_start', 'mcp_stop', 'mcp_call', 'mcp_add']);
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
- 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', 'mcp_call', 'mcp_add']);
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
- default:
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 { getMCPManager } = await import('./mcp-manager.js');
1146
- const manager = getMCPManager();
1147
- const mcps = manager.listMCPs();
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 = ['Available MCPs:\n'];
1152
- for (const mcp of mcps) {
1153
- const status = mcp.running ? '🟢 Running' : mcp.enabled ? '⚪ Stopped' : '⚫ Disabled';
1154
- const builtIn = mcp.builtIn ? ' [Built-in]' : '';
1155
- lines.push(`${status} ${mcp.name}${builtIn}`);
1156
- lines.push(` ${mcp.description}`);
1157
- if (mcp.running && mcp.tools) {
1158
- lines.push(` Tools: ${mcp.tools.map(t => t.name).join(', ')}`);
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 mcpStart(input) {
1165
- const { getMCPManager } = await import('./mcp-manager.js');
1166
- const manager = getMCPManager();
1167
- const result = await manager.startMCP(input.name);
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
- return `✓ Stopped MCP "${input.name}".`;
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
- return typeof result.result === 'string'
1192
- ? result.result
1193
- : JSON.stringify(result.result, null, 2);
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
- return `✓ Uninstalled MCP "${input.name}".`;
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: return `Unknown tool: ${name}`;
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 {};