mini-coder 0.5.11 → 0.5.13
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/BENCHMARK.md +107 -0
- package/PROGRESS.md +4 -0
- package/README.md +66 -21
- package/assets/mc-claude-smart.png +0 -0
- package/assets/mc-gpt-smart.png +0 -0
- package/benchmark-baseline.sh +15 -0
- package/benchmark-loop.sh +19 -0
- package/bun.lock +265 -90
- package/package.json +8 -7
- package/skills-lock.json +15 -0
- package/src/agent.ts +20 -0
- package/src/cli.ts +2 -1
- package/src/headless.ts +152 -38
- package/src/index.ts +107 -143
- package/src/input.ts +13 -1
- package/src/mcp.ts +609 -0
- package/src/prompt.ts +10 -12
- package/src/session-message.ts +393 -0
- package/src/session.ts +102 -396
- package/src/settings.ts +218 -22
- package/src/shared.ts +39 -0
- package/src/skills.ts +12 -3
- package/src/submit.ts +20 -25
- package/src/text.ts +71 -0
- package/src/theme.ts +186 -3
- package/src/tool-common.ts +93 -0
- package/src/tool-grep.ts +606 -0
- package/src/tool-read.ts +313 -0
- package/src/tool-shell.ts +1001 -0
- package/src/tools.ts +190 -995
- package/src/ui/agent.ts +206 -110
- package/src/ui/commands.test.ts +489 -17
- package/src/ui/commands.ts +231 -55
- package/src/ui/conversation.test.ts +585 -0
- package/src/ui/conversation.ts +878 -455
- package/src/ui/help.ts +27 -11
- package/src/ui/input.test.ts +1 -43
- package/src/ui/runtime.ts +69 -0
- package/src/ui.ts +422 -185
- package/src/plugins.ts +0 -183
package/src/index.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Entry point for mini-coder.
|
|
3
3
|
*
|
|
4
|
-
* Discovers available LLM providers, loads
|
|
5
|
-
*
|
|
6
|
-
* the TUI.
|
|
4
|
+
* Discovers available LLM providers, loads configured MCP tools,
|
|
5
|
+
* loads prompt context (AGENTS.md, skills, and theme), opens the session
|
|
6
|
+
* database, selects a model, and starts the TUI.
|
|
7
7
|
*
|
|
8
8
|
* @module
|
|
9
9
|
*/
|
|
@@ -14,7 +14,6 @@ import { basename, dirname, join } from "node:path";
|
|
|
14
14
|
import { isDeepStrictEqual } from "node:util";
|
|
15
15
|
import type {
|
|
16
16
|
KnownProvider,
|
|
17
|
-
Message,
|
|
18
17
|
Model,
|
|
19
18
|
OAuthCredentials,
|
|
20
19
|
ThinkingLevel,
|
|
@@ -33,15 +32,8 @@ import {
|
|
|
33
32
|
} from "./cli.ts";
|
|
34
33
|
import { getErrorMessage } from "./errors.ts";
|
|
35
34
|
import { type GitState, getGitState } from "./git.ts";
|
|
35
|
+
import { discoverMcpServers, type McpServerState } from "./mcp.ts";
|
|
36
36
|
import { canonicalizePath } from "./paths.ts";
|
|
37
|
-
import {
|
|
38
|
-
type AgentContext,
|
|
39
|
-
destroyPlugins,
|
|
40
|
-
initPlugins,
|
|
41
|
-
type LoadedPlugin,
|
|
42
|
-
loadPluginConfig,
|
|
43
|
-
type PluginEntry,
|
|
44
|
-
} from "./plugins.ts";
|
|
45
37
|
import {
|
|
46
38
|
type AgentsMdFile,
|
|
47
39
|
buildSystemPrompt,
|
|
@@ -50,10 +42,8 @@ import {
|
|
|
50
42
|
} from "./prompt.ts";
|
|
51
43
|
import {
|
|
52
44
|
appendMessage,
|
|
53
|
-
|
|
54
|
-
computeStats,
|
|
45
|
+
createConversationSnapshot,
|
|
55
46
|
createSession,
|
|
56
|
-
filterModelMessages,
|
|
57
47
|
type loadMessages,
|
|
58
48
|
openDatabase,
|
|
59
49
|
type Session,
|
|
@@ -62,21 +52,26 @@ import {
|
|
|
62
52
|
} from "./session.ts";
|
|
63
53
|
import {
|
|
64
54
|
type CustomProvider,
|
|
65
|
-
|
|
55
|
+
loadStartupSettings,
|
|
56
|
+
mergeUserSettings,
|
|
66
57
|
resolveStartupSettings,
|
|
67
58
|
type UserSettings,
|
|
68
59
|
} from "./settings.ts";
|
|
69
60
|
import { discoverSkills, type Skill } from "./skills.ts";
|
|
70
|
-
import { DEFAULT_THEME,
|
|
61
|
+
import { DEFAULT_THEME, type Theme } from "./theme.ts";
|
|
71
62
|
import {
|
|
63
|
+
createTodoReadToolHandler,
|
|
64
|
+
createTodoWriteToolHandler,
|
|
72
65
|
editTool,
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
executeTodoRead,
|
|
77
|
-
executeTodoWrite,
|
|
66
|
+
editToolHandler,
|
|
67
|
+
grepTool,
|
|
68
|
+
grepToolHandler,
|
|
78
69
|
readImageTool,
|
|
70
|
+
readImageToolHandler,
|
|
71
|
+
readTool,
|
|
72
|
+
readToolHandler,
|
|
79
73
|
shellTool,
|
|
74
|
+
shellToolHandler,
|
|
80
75
|
todoReadTool,
|
|
81
76
|
todoWriteTool,
|
|
82
77
|
} from "./tools.ts";
|
|
@@ -92,9 +87,6 @@ const DATA_DIR = join(homedir(), ".config", "mini-coder");
|
|
|
92
87
|
/** SQLite database path. */
|
|
93
88
|
const DB_PATH = join(DATA_DIR, "mini-coder.db");
|
|
94
89
|
|
|
95
|
-
/** Plugin config file path. */
|
|
96
|
-
const PLUGIN_CONFIG_PATH = join(DATA_DIR, "plugins.json");
|
|
97
|
-
|
|
98
90
|
/** OAuth credentials file path. */
|
|
99
91
|
const AUTH_PATH = join(DATA_DIR, "auth.json");
|
|
100
92
|
|
|
@@ -359,26 +351,6 @@ function selectModel(
|
|
|
359
351
|
// Tool wiring
|
|
360
352
|
// ---------------------------------------------------------------------------
|
|
361
353
|
|
|
362
|
-
/** Built-in tool handlers keyed by tool name. */
|
|
363
|
-
const BUILTIN_HANDLERS: Record<string, ToolHandler> = {
|
|
364
|
-
edit: (args, cwd) =>
|
|
365
|
-
executeEdit(
|
|
366
|
-
{
|
|
367
|
-
path: args.path as string,
|
|
368
|
-
oldText: args.oldText as string,
|
|
369
|
-
newText: args.newText as string,
|
|
370
|
-
},
|
|
371
|
-
cwd,
|
|
372
|
-
),
|
|
373
|
-
shell: (args, cwd, signal, onUpdate) =>
|
|
374
|
-
executeShell({ command: args.command as string }, cwd, {
|
|
375
|
-
...(signal ? { signal } : {}),
|
|
376
|
-
...(onUpdate ? { onUpdate } : {}),
|
|
377
|
-
}),
|
|
378
|
-
readImage: (args, cwd) =>
|
|
379
|
-
executeReadImage({ path: args.path as string }, cwd),
|
|
380
|
-
};
|
|
381
|
-
|
|
382
354
|
/**
|
|
383
355
|
* Build tool definitions and handler map for the current model.
|
|
384
356
|
*
|
|
@@ -387,49 +359,41 @@ const BUILTIN_HANDLERS: Record<string, ToolHandler> = {
|
|
|
387
359
|
*/
|
|
388
360
|
function buildTools(
|
|
389
361
|
model: Model<string>,
|
|
390
|
-
plugins: LoadedPlugin[],
|
|
391
362
|
messages: AppState["messages"],
|
|
363
|
+
mcpServers: readonly McpServerState[],
|
|
392
364
|
): { tools: Tool[]; toolHandlers: Map<string, ToolHandler> } {
|
|
393
|
-
const tools: Tool[] = [
|
|
365
|
+
const tools: Tool[] = [
|
|
366
|
+
shellTool,
|
|
367
|
+
readTool,
|
|
368
|
+
grepTool,
|
|
369
|
+
editTool,
|
|
370
|
+
todoWriteTool,
|
|
371
|
+
todoReadTool,
|
|
372
|
+
];
|
|
394
373
|
const toolHandlers = new Map<string, ToolHandler>([
|
|
395
|
-
[
|
|
396
|
-
[
|
|
397
|
-
[
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
{
|
|
402
|
-
todos: Array.isArray(args.todos)
|
|
403
|
-
? (args.todos as Array<{
|
|
404
|
-
content: string;
|
|
405
|
-
status: "pending" | "in_progress" | "completed" | "cancelled";
|
|
406
|
-
}>)
|
|
407
|
-
: [],
|
|
408
|
-
},
|
|
409
|
-
messages,
|
|
410
|
-
),
|
|
411
|
-
],
|
|
412
|
-
[todoReadTool.name, () => executeTodoRead(messages)],
|
|
374
|
+
[shellTool.name, shellToolHandler],
|
|
375
|
+
[readTool.name, readToolHandler],
|
|
376
|
+
[grepTool.name, grepToolHandler],
|
|
377
|
+
[editTool.name, editToolHandler],
|
|
378
|
+
[todoWriteTool.name, createTodoWriteToolHandler(messages)],
|
|
379
|
+
[todoReadTool.name, createTodoReadToolHandler(messages)],
|
|
413
380
|
]);
|
|
414
381
|
|
|
382
|
+
for (const server of mcpServers) {
|
|
383
|
+
if (!server.enabled || !server.connected) {
|
|
384
|
+
continue;
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
tools.push(...server.tools);
|
|
388
|
+
for (const [name, handler] of server.toolHandlers) {
|
|
389
|
+
toolHandlers.set(name, handler);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
|
|
415
393
|
// Conditionally register readImage for vision-capable models
|
|
416
394
|
if (model.input.includes("image")) {
|
|
417
395
|
tools.push(readImageTool);
|
|
418
|
-
toolHandlers.set(readImageTool.name,
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
// Add plugin tools
|
|
422
|
-
for (const plugin of plugins) {
|
|
423
|
-
if (plugin.result.tools) {
|
|
424
|
-
for (const tool of plugin.result.tools) {
|
|
425
|
-
tools.push(tool);
|
|
426
|
-
}
|
|
427
|
-
}
|
|
428
|
-
if (plugin.result.toolHandlers) {
|
|
429
|
-
for (const [name, handler] of plugin.result.toolHandlers) {
|
|
430
|
-
toolHandlers.set(name, handler);
|
|
431
|
-
}
|
|
432
|
-
}
|
|
396
|
+
toolHandlers.set(readImageTool.name, readImageToolHandler);
|
|
433
397
|
}
|
|
434
398
|
|
|
435
399
|
return { tools, toolHandlers };
|
|
@@ -451,21 +415,13 @@ function getSkillScanPaths(cwd: string, gitRoot: string | null): string[] {
|
|
|
451
415
|
];
|
|
452
416
|
}
|
|
453
417
|
|
|
454
|
-
/** Load AGENTS.md files, skills,
|
|
455
|
-
export async function loadPromptContext(
|
|
456
|
-
messages: readonly Message[],
|
|
457
|
-
opts?: {
|
|
458
|
-
cwd?: string;
|
|
459
|
-
pluginEntries?: PluginEntry[];
|
|
460
|
-
pluginConfigPath?: string;
|
|
461
|
-
},
|
|
462
|
-
): Promise<{
|
|
418
|
+
/** Load AGENTS.md files, skills, git state, and the active theme. */
|
|
419
|
+
export async function loadPromptContext(opts?: { cwd?: string }): Promise<{
|
|
463
420
|
cwd: string;
|
|
464
421
|
canonicalCwd: string;
|
|
465
422
|
git: GitState | null;
|
|
466
423
|
agentsMd: AgentsMdFile[];
|
|
467
424
|
skills: Skill[];
|
|
468
|
-
plugins: LoadedPlugin[];
|
|
469
425
|
theme: Theme;
|
|
470
426
|
}> {
|
|
471
427
|
const cwd = opts?.cwd ?? process.cwd();
|
|
@@ -481,20 +437,6 @@ export async function loadPromptContext(
|
|
|
481
437
|
);
|
|
482
438
|
const agentsMd = discoverAgentsMd(cwd, scanRoot, join(home, ".agents"));
|
|
483
439
|
const skills = discoverSkills(getSkillScanPaths(canonicalCwd, gitRoot));
|
|
484
|
-
const pluginEntries =
|
|
485
|
-
opts?.pluginEntries ??
|
|
486
|
-
loadPluginConfig(opts?.pluginConfigPath ?? PLUGIN_CONFIG_PATH);
|
|
487
|
-
const context: AgentContext = {
|
|
488
|
-
cwd,
|
|
489
|
-
messages,
|
|
490
|
-
dataDir: DATA_DIR,
|
|
491
|
-
};
|
|
492
|
-
const plugins = await initPlugins(pluginEntries, context, (entry, err) => {
|
|
493
|
-
console.error(`Plugin "${entry.name}" failed to init: ${err.message}`);
|
|
494
|
-
});
|
|
495
|
-
const themeOverrides = plugins
|
|
496
|
-
.map((plugin) => plugin.result.theme)
|
|
497
|
-
.filter((theme): theme is Partial<Theme> => theme != null);
|
|
498
440
|
|
|
499
441
|
return {
|
|
500
442
|
cwd,
|
|
@@ -502,8 +444,38 @@ export async function loadPromptContext(
|
|
|
502
444
|
git,
|
|
503
445
|
agentsMd,
|
|
504
446
|
skills,
|
|
505
|
-
|
|
506
|
-
|
|
447
|
+
theme: DEFAULT_THEME,
|
|
448
|
+
};
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
/**
|
|
452
|
+
* Load global and repo-local settings for the current launch.
|
|
453
|
+
*
|
|
454
|
+
* The repo-local overlay is read only and is loaded only when a git root is
|
|
455
|
+
* known. Invalid startup content in either file is treated as empty settings.
|
|
456
|
+
*
|
|
457
|
+
* @param opts - Optional settings path and git-root override for tests.
|
|
458
|
+
* @returns Global settings, repo-local overlay settings, and the merged result.
|
|
459
|
+
*/
|
|
460
|
+
export function loadUserSettingsForLaunch(opts?: {
|
|
461
|
+
settingsPath?: string;
|
|
462
|
+
gitRoot?: string | null;
|
|
463
|
+
}): {
|
|
464
|
+
settings: UserSettings;
|
|
465
|
+
repoSettings: UserSettings;
|
|
466
|
+
effectiveSettings: UserSettings;
|
|
467
|
+
} {
|
|
468
|
+
const settingsPath = opts?.settingsPath ?? SETTINGS_PATH;
|
|
469
|
+
const gitRoot = opts?.gitRoot ?? null;
|
|
470
|
+
const settings = loadStartupSettings(settingsPath);
|
|
471
|
+
const repoSettings = gitRoot
|
|
472
|
+
? loadStartupSettings(join(gitRoot, ".mini-coder", "settings.json"))
|
|
473
|
+
: {};
|
|
474
|
+
|
|
475
|
+
return {
|
|
476
|
+
settings,
|
|
477
|
+
repoSettings,
|
|
478
|
+
effectiveSettings: mergeUserSettings(settings, repoSettings),
|
|
507
479
|
};
|
|
508
480
|
}
|
|
509
481
|
|
|
@@ -512,25 +484,17 @@ export async function reloadPromptContext(
|
|
|
512
484
|
state: AppState,
|
|
513
485
|
runtime?: {
|
|
514
486
|
loadPromptContext?: typeof loadPromptContext;
|
|
515
|
-
destroyPlugins?: typeof destroyPlugins;
|
|
516
487
|
},
|
|
517
488
|
): Promise<void> {
|
|
518
489
|
const loadContext = runtime?.loadPromptContext ?? loadPromptContext;
|
|
519
|
-
const
|
|
520
|
-
const previousPlugins = state.plugins;
|
|
521
|
-
const context = await loadContext(filterModelMessages(state.messages));
|
|
490
|
+
const context = await loadContext();
|
|
522
491
|
|
|
523
492
|
state.cwd = context.cwd;
|
|
524
493
|
state.canonicalCwd = context.canonicalCwd;
|
|
525
494
|
state.git = context.git;
|
|
526
495
|
state.agentsMd = context.agentsMd;
|
|
527
496
|
state.skills = context.skills;
|
|
528
|
-
state.plugins = context.plugins;
|
|
529
497
|
state.theme = context.theme;
|
|
530
|
-
|
|
531
|
-
await destroyLoadedPlugins(previousPlugins, (entry, err) => {
|
|
532
|
-
console.error(`Plugin "${entry.name}" failed to destroy: ${err.message}`);
|
|
533
|
-
});
|
|
534
498
|
}
|
|
535
499
|
|
|
536
500
|
// ---------------------------------------------------------------------------
|
|
@@ -557,9 +521,7 @@ export interface AppState {
|
|
|
557
521
|
agentsMd: AgentsMdFile[];
|
|
558
522
|
/** Discovered skills. */
|
|
559
523
|
skills: Skill[];
|
|
560
|
-
/**
|
|
561
|
-
plugins: LoadedPlugin[];
|
|
562
|
-
/** Active theme (default + plugin overrides). */
|
|
524
|
+
/** Active theme. */
|
|
563
525
|
theme: Theme;
|
|
564
526
|
/** Version label shown in the empty conversation banner. */
|
|
565
527
|
versionLabel: string;
|
|
@@ -571,6 +533,8 @@ export interface AppState {
|
|
|
571
533
|
oauthCredentials: Record<string, OAuthCredentials>;
|
|
572
534
|
/** Loaded global user settings. */
|
|
573
535
|
settings: UserSettings;
|
|
536
|
+
/** Loaded repo-local settings overlay for the current app run. */
|
|
537
|
+
repoSettings: UserSettings;
|
|
574
538
|
/** Absolute path to the global settings file. */
|
|
575
539
|
settingsPath: string;
|
|
576
540
|
/** Working directory as entered by the user/shell (for display and tool execution). */
|
|
@@ -589,9 +553,11 @@ export interface AppState {
|
|
|
589
553
|
showReasoning: boolean;
|
|
590
554
|
/** Whether to show full (un-truncated) tool output. */
|
|
591
555
|
verbose: boolean;
|
|
556
|
+
/** Configured MCP servers, including their current enabled/disabled state. */
|
|
557
|
+
mcpServers: McpServerState[];
|
|
592
558
|
/** Models discovered from custom OpenAI-compatible providers. */
|
|
593
559
|
customModels: Model<string>[];
|
|
594
|
-
/** Warnings from startup (e.g. unreachable custom providers). */
|
|
560
|
+
/** Warnings from startup (e.g. unreachable custom providers or MCP servers). */
|
|
595
561
|
startupWarnings: string[];
|
|
596
562
|
}
|
|
597
563
|
|
|
@@ -609,13 +575,17 @@ export async function init(): Promise<AppState> {
|
|
|
609
575
|
// Discover providers (env + OAuth)
|
|
610
576
|
const { providers, oauthCredentials } = await discoverProviders();
|
|
611
577
|
|
|
612
|
-
|
|
613
|
-
const settings =
|
|
578
|
+
const promptContext = await loadPromptContext({ cwd });
|
|
579
|
+
const { settings, repoSettings, effectiveSettings } =
|
|
580
|
+
loadUserSettingsForLaunch({
|
|
581
|
+
settingsPath: SETTINGS_PATH,
|
|
582
|
+
gitRoot: promptContext.git?.root ?? null,
|
|
583
|
+
});
|
|
614
584
|
|
|
615
|
-
// Discover custom providers from settings
|
|
585
|
+
// Discover custom providers from effective settings
|
|
616
586
|
const builtInProviderNames = new Set(providers.keys());
|
|
617
587
|
const customResult = await discoverCustomProviders(
|
|
618
|
-
|
|
588
|
+
effectiveSettings.customProviders ?? [],
|
|
619
589
|
builtInProviderNames,
|
|
620
590
|
);
|
|
621
591
|
|
|
@@ -624,10 +594,12 @@ export async function init(): Promise<AppState> {
|
|
|
624
594
|
providers.set(name, key);
|
|
625
595
|
}
|
|
626
596
|
|
|
597
|
+
const mcpResult = await discoverMcpServers(effectiveSettings.mcp);
|
|
598
|
+
|
|
627
599
|
const builtInModels = listAvailableModels(providers);
|
|
628
600
|
const availableModels = [...builtInModels, ...customResult.models];
|
|
629
601
|
const startup = resolveStartupSettings(
|
|
630
|
-
|
|
602
|
+
effectiveSettings,
|
|
631
603
|
availableModels.map((model) => `${model.provider}/${model.id}`),
|
|
632
604
|
);
|
|
633
605
|
const model = selectModel(availableModels, startup.modelId);
|
|
@@ -635,30 +607,25 @@ export async function init(): Promise<AppState> {
|
|
|
635
607
|
// Open database. Sessions are created lazily on the first user message.
|
|
636
608
|
const db = openDatabase(DB_PATH);
|
|
637
609
|
const effort = startup.effort;
|
|
638
|
-
const
|
|
639
|
-
const stats = computeStats(messages);
|
|
640
|
-
const contextTokens = computeContextTokens(messages);
|
|
641
|
-
const promptContext = await loadPromptContext(filterModelMessages(messages), {
|
|
642
|
-
cwd,
|
|
643
|
-
});
|
|
610
|
+
const conversation = createConversationSnapshot();
|
|
644
611
|
|
|
645
612
|
return {
|
|
646
613
|
db,
|
|
647
614
|
session: null,
|
|
648
615
|
model,
|
|
649
616
|
effort,
|
|
650
|
-
messages,
|
|
651
|
-
stats,
|
|
652
|
-
contextTokens,
|
|
617
|
+
messages: conversation.messages,
|
|
618
|
+
stats: conversation.stats,
|
|
619
|
+
contextTokens: conversation.contextTokens,
|
|
653
620
|
agentsMd: promptContext.agentsMd,
|
|
654
621
|
skills: promptContext.skills,
|
|
655
|
-
plugins: promptContext.plugins,
|
|
656
622
|
theme: promptContext.theme,
|
|
657
623
|
versionLabel: resolveAppVersionLabel(),
|
|
658
624
|
git: promptContext.git,
|
|
659
625
|
providers,
|
|
660
626
|
oauthCredentials,
|
|
661
627
|
settings,
|
|
628
|
+
repoSettings,
|
|
662
629
|
settingsPath: SETTINGS_PATH,
|
|
663
630
|
cwd: promptContext.cwd,
|
|
664
631
|
canonicalCwd: promptContext.canonicalCwd,
|
|
@@ -668,8 +635,9 @@ export async function init(): Promise<AppState> {
|
|
|
668
635
|
queuedUserMessages: [],
|
|
669
636
|
showReasoning: startup.showReasoning,
|
|
670
637
|
verbose: startup.verbose,
|
|
638
|
+
mcpServers: mcpResult.servers,
|
|
671
639
|
customModels: customResult.models,
|
|
672
|
-
startupWarnings: customResult.warnings,
|
|
640
|
+
startupWarnings: [...customResult.warnings, ...mcpResult.warnings],
|
|
673
641
|
};
|
|
674
642
|
}
|
|
675
643
|
|
|
@@ -707,9 +675,6 @@ export function buildPrompt(state: AppState): string {
|
|
|
707
675
|
git: state.git,
|
|
708
676
|
agentsMd: state.agentsMd,
|
|
709
677
|
skills: state.skills,
|
|
710
|
-
pluginSuffixes: state.plugins
|
|
711
|
-
.map((p) => p.result.systemPromptSuffix)
|
|
712
|
-
.filter((s): s is string => s != null),
|
|
713
678
|
});
|
|
714
679
|
}
|
|
715
680
|
|
|
@@ -719,7 +684,7 @@ export function buildToolList(state: AppState): {
|
|
|
719
684
|
toolHandlers: Map<string, ToolHandler>;
|
|
720
685
|
} {
|
|
721
686
|
if (!state.model) return { tools: [], toolHandlers: new Map() };
|
|
722
|
-
return buildTools(state.model, state.
|
|
687
|
+
return buildTools(state.model, state.messages, state.mcpServers);
|
|
723
688
|
}
|
|
724
689
|
|
|
725
690
|
/**
|
|
@@ -768,9 +733,7 @@ export function getAvailableModels(state: AppState): Model<string>[] {
|
|
|
768
733
|
|
|
769
734
|
/** Clean up resources on shutdown. */
|
|
770
735
|
export async function shutdown(state: AppState): Promise<void> {
|
|
771
|
-
await
|
|
772
|
-
console.error(`Plugin "${entry.name}" failed to destroy: ${err.message}`);
|
|
773
|
-
});
|
|
736
|
+
await Promise.allSettled(state.mcpServers.map((server) => server.close()));
|
|
774
737
|
state.db.close();
|
|
775
738
|
}
|
|
776
739
|
|
|
@@ -797,7 +760,8 @@ type HeadlessCliStopReason = "stop" | "length" | "error" | "aborted";
|
|
|
797
760
|
*
|
|
798
761
|
* Non-TTY detection only decides whether headless mode should run at all.
|
|
799
762
|
* Once headless mode is selected, `--json` is the only switch that chooses
|
|
800
|
-
* NDJSON streaming versus
|
|
763
|
+
* NDJSON streaming versus the default text mode (stdout final answer plus
|
|
764
|
+
* stderr activity snippets).
|
|
801
765
|
*
|
|
802
766
|
* @param state - Initialized application state for the run.
|
|
803
767
|
* @param cli - Parsed CLI options.
|
package/src/input.ts
CHANGED
|
@@ -23,6 +23,7 @@ export const COMMANDS = [
|
|
|
23
23
|
"undo",
|
|
24
24
|
"reasoning",
|
|
25
25
|
"verbose",
|
|
26
|
+
"mcp",
|
|
26
27
|
"todo",
|
|
27
28
|
"login",
|
|
28
29
|
"logout",
|
|
@@ -31,8 +32,11 @@ export const COMMANDS = [
|
|
|
31
32
|
"effort",
|
|
32
33
|
] as const;
|
|
33
34
|
|
|
35
|
+
/** Slash helper that opens the interactive skill picker when submitted alone. */
|
|
36
|
+
export const SKILL_COMMAND = "skill" as const;
|
|
37
|
+
|
|
34
38
|
/** A recognized slash command name. */
|
|
35
|
-
type Command = (typeof COMMANDS)[number];
|
|
39
|
+
type Command = (typeof COMMANDS)[number] | typeof SKILL_COMMAND;
|
|
36
40
|
|
|
37
41
|
const COMMAND_SET: ReadonlySet<string> = new Set(COMMANDS);
|
|
38
42
|
|
|
@@ -73,6 +77,14 @@ function isCommand(value: string): value is Command {
|
|
|
73
77
|
}
|
|
74
78
|
|
|
75
79
|
function parseSlashInput(trimmed: string): ParsedInput | null {
|
|
80
|
+
if (trimmed === `/${SKILL_COMMAND}`) {
|
|
81
|
+
return {
|
|
82
|
+
type: "command",
|
|
83
|
+
command: SKILL_COMMAND,
|
|
84
|
+
args: "",
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
76
88
|
const skillMatch = trimmed.match(/^\/skill:(\S+)(?:\s+(.*))?$/s);
|
|
77
89
|
if (skillMatch?.[1]) {
|
|
78
90
|
return {
|