@miphamai/cli 0.6.4 → 0.6.6
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/package.json +1 -1
- package/src/core/engine.ts +8 -0
- package/src/index.tsx +26 -0
- package/src/plugin/plugin-loader.ts +85 -16
- package/src/shared/types.ts +2 -0
- package/src/skills/loader.ts +2 -1
- package/src/tools/agent/skill.ts +4 -3
- package/src/ui/app.tsx +8 -4
package/package.json
CHANGED
package/src/core/engine.ts
CHANGED
|
@@ -8,12 +8,14 @@ import type { AgentRegistry } from '../agent/agent-registry'
|
|
|
8
8
|
import { analyzeForMemory } from './memory/memory-writer'
|
|
9
9
|
import { getMemoryManager } from './memory/memory-loader'
|
|
10
10
|
import type { AgentViewManager } from '../agent-view/agent-view-manager'
|
|
11
|
+
import type { SkillsLoader } from '../skills/loader'
|
|
11
12
|
|
|
12
13
|
export class QueryEngine {
|
|
13
14
|
private hookEngine?: HookEngine
|
|
14
15
|
private artifactServer?: ArtifactServer
|
|
15
16
|
private agentRegistry?: AgentRegistry
|
|
16
17
|
private agentViewManager?: AgentViewManager
|
|
18
|
+
private skillsLoader?: SkillsLoader
|
|
17
19
|
private goal?: string
|
|
18
20
|
private maxGoalLoops = 20
|
|
19
21
|
private lastAssistantContent?: string
|
|
@@ -55,6 +57,11 @@ export class QueryEngine {
|
|
|
55
57
|
return this.agentViewManager
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
/** Register the SkillsLoader for Skill tool context injection. */
|
|
61
|
+
setSkillsLoader(loader: SkillsLoader): void {
|
|
62
|
+
this.skillsLoader = loader
|
|
63
|
+
}
|
|
64
|
+
|
|
58
65
|
/** Set the session goal for goal-driven execution. */
|
|
59
66
|
setGoal(goal: string): void {
|
|
60
67
|
this.goal = goal
|
|
@@ -393,6 +400,7 @@ export class QueryEngine {
|
|
|
393
400
|
sessionId: 'session-1',
|
|
394
401
|
provider: this.registry.getActive().config.id,
|
|
395
402
|
model: this.registry.getActiveModel(),
|
|
403
|
+
skillsLoader: this.skillsLoader,
|
|
396
404
|
registry: this.registry,
|
|
397
405
|
toolRegistry: this.tools,
|
|
398
406
|
artifactServer: this.artifactServer,
|
package/src/index.tsx
CHANGED
|
@@ -9,9 +9,11 @@ import { QueryEngine } from './core/engine'
|
|
|
9
9
|
import { SessionStore } from './core/session-store'
|
|
10
10
|
import { SkillsLoader } from './skills/loader'
|
|
11
11
|
import { PluginManager } from './plugin/plugin-manager'
|
|
12
|
+
import { loadPlugins } from './plugin/plugin-loader'
|
|
12
13
|
import { createToolRegistry } from './tools'
|
|
13
14
|
import { McpClient } from './mcp/client'
|
|
14
15
|
import { registerMcpServerTools } from './mcp/registry'
|
|
16
|
+
import { AgentRegistry } from './agent/agent-registry'
|
|
15
17
|
import { HookEngine } from './core/hooks'
|
|
16
18
|
import { ArtifactServer } from './artifacts/server'
|
|
17
19
|
import { ARTIFACTS_DIR, ARTIFACT_PORT, MIPHAM_DIR } from './shared/constants'
|
|
@@ -63,10 +65,17 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
63
65
|
// Load skills
|
|
64
66
|
const skillsLoader = new SkillsLoader()
|
|
65
67
|
skillsLoader.loadBuiltin(process.cwd())
|
|
68
|
+
skillsLoader.loadUserSkills()
|
|
66
69
|
if (config.skills?.paths) {
|
|
67
70
|
skillsLoader.loadExternal(config.skills.paths)
|
|
68
71
|
}
|
|
69
72
|
|
|
73
|
+
// Inject skills system-reminder into system prompt for AI auto-triggering
|
|
74
|
+
const skillsReminder = skillsLoader.buildSystemReminder()
|
|
75
|
+
if (skillsReminder) {
|
|
76
|
+
instructions.setSkillsReminder(skillsReminder)
|
|
77
|
+
}
|
|
78
|
+
|
|
70
79
|
// Initialize plugin manager
|
|
71
80
|
const pluginManager = new PluginManager()
|
|
72
81
|
|
|
@@ -134,6 +143,23 @@ export async function runApp(options: RunOptions): Promise<void> {
|
|
|
134
143
|
engine.setHookEngine(hookEngine)
|
|
135
144
|
engine.setArtifactServer(artifactServer)
|
|
136
145
|
engine.setAgentViewManager(agentViewManager)
|
|
146
|
+
engine.setSkillsLoader(skillsLoader)
|
|
147
|
+
|
|
148
|
+
// Initialize agent registry and load plugin agents/skills/MCP/hooks
|
|
149
|
+
const agentRegistry = new AgentRegistry()
|
|
150
|
+
agentRegistry.loadUserAgents()
|
|
151
|
+
agentRegistry.loadProjectAgents(process.cwd())
|
|
152
|
+
engine.setAgentRegistry(agentRegistry)
|
|
153
|
+
|
|
154
|
+
loadPlugins(
|
|
155
|
+
pluginManager,
|
|
156
|
+
agentRegistry,
|
|
157
|
+
skillsLoader,
|
|
158
|
+
hookEngine,
|
|
159
|
+
McpClient.getInstance(),
|
|
160
|
+
tools,
|
|
161
|
+
)
|
|
162
|
+
|
|
137
163
|
engine.setupContextSummarizer()
|
|
138
164
|
|
|
139
165
|
// Auto-save session on exit
|
|
@@ -1,36 +1,105 @@
|
|
|
1
|
-
import type { PluginManager } from './plugin-manager'
|
|
2
1
|
import { join } from 'node:path'
|
|
3
|
-
import { existsSync } from 'node:fs'
|
|
2
|
+
import { existsSync, readdirSync, readFileSync } from 'node:fs'
|
|
3
|
+
import type { PluginManager } from './plugin-manager'
|
|
4
|
+
import type { AgentRegistry } from '../agent/agent-registry'
|
|
5
|
+
import type { SkillsLoader } from '../skills/loader'
|
|
6
|
+
import type { HookEngine } from '../core/hooks'
|
|
7
|
+
import type { McpClient } from '../mcp/client'
|
|
8
|
+
import { registerMcpServerTools } from '../mcp/registry'
|
|
9
|
+
import type { McpServerConfig, ToolDefinition, HookDefinition } from '../shared/types'
|
|
4
10
|
|
|
11
|
+
/**
|
|
12
|
+
* Activate all enabled plugins — load their agents, skills, MCP servers, and hooks.
|
|
13
|
+
*
|
|
14
|
+
* Called once at startup after the core subsystems (agent registry, skills loader,
|
|
15
|
+
* hook engine, MCP client, tool registry) are initialized.
|
|
16
|
+
*/
|
|
5
17
|
export function loadPlugins(
|
|
6
18
|
pluginManager: PluginManager,
|
|
7
|
-
|
|
8
|
-
skillsLoader:
|
|
19
|
+
agentRegistry: AgentRegistry,
|
|
20
|
+
skillsLoader: SkillsLoader,
|
|
21
|
+
hookEngine: HookEngine,
|
|
22
|
+
mcpClient: McpClient,
|
|
23
|
+
toolsMap: Map<string, ToolDefinition>,
|
|
9
24
|
): void {
|
|
10
25
|
for (const plugin of pluginManager.getEnabled()) {
|
|
11
|
-
//
|
|
26
|
+
// ── Custom agents ──
|
|
12
27
|
const agentsDir = join(plugin.path, 'agents')
|
|
13
28
|
if (existsSync(agentsDir)) {
|
|
14
|
-
|
|
29
|
+
try {
|
|
30
|
+
agentRegistry.loadDirectory(agentsDir, 'user')
|
|
31
|
+
} catch (err) {
|
|
32
|
+
process.stderr.write(
|
|
33
|
+
`[plugin] Failed to load agents from "${plugin.name}": ${String(err)}\n`,
|
|
34
|
+
)
|
|
35
|
+
}
|
|
15
36
|
}
|
|
16
37
|
|
|
17
|
-
//
|
|
38
|
+
// ── Custom skills ──
|
|
18
39
|
const skillsDir = join(plugin.path, 'skills')
|
|
19
40
|
if (existsSync(skillsDir)) {
|
|
20
|
-
|
|
41
|
+
try {
|
|
42
|
+
skillsLoader.loadExternal([skillsDir])
|
|
43
|
+
} catch (err) {
|
|
44
|
+
process.stderr.write(
|
|
45
|
+
`[plugin] Failed to load skills from "${plugin.name}": ${String(err)}\n`,
|
|
46
|
+
)
|
|
47
|
+
}
|
|
21
48
|
}
|
|
22
49
|
|
|
23
|
-
//
|
|
50
|
+
// ── MCP servers ──
|
|
24
51
|
const mcpDir = join(plugin.path, 'mcp-servers')
|
|
25
52
|
if (existsSync(mcpDir)) {
|
|
26
|
-
|
|
53
|
+
try {
|
|
54
|
+
const entries = readdirSync(mcpDir)
|
|
55
|
+
for (const entry of entries) {
|
|
56
|
+
if (!entry.endsWith('.json')) continue
|
|
57
|
+
try {
|
|
58
|
+
const raw = readFileSync(join(mcpDir, entry), 'utf-8')
|
|
59
|
+
const cfg = JSON.parse(raw) as McpServerConfig
|
|
60
|
+
if (cfg.name && cfg.command) {
|
|
61
|
+
mcpClient
|
|
62
|
+
.connect(cfg)
|
|
63
|
+
.then(() => {
|
|
64
|
+
const count = registerMcpServerTools(cfg.name, toolsMap)
|
|
65
|
+
if (count > 0) {
|
|
66
|
+
process.stderr.write(
|
|
67
|
+
`[plugin] "${plugin.name}": registered ${count} MCP tools from "${cfg.name}"\n`,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
})
|
|
71
|
+
.catch((err: unknown) => {
|
|
72
|
+
process.stderr.write(
|
|
73
|
+
`[plugin] Failed to connect MCP "${cfg.name}" from "${plugin.name}": ${String(err)}\n`,
|
|
74
|
+
)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
} catch {
|
|
78
|
+
// skip unparseable MCP config files
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
process.stderr.write(
|
|
83
|
+
`[plugin] Failed to load MCP configs from "${plugin.name}": ${String(err)}\n`,
|
|
84
|
+
)
|
|
85
|
+
}
|
|
27
86
|
}
|
|
28
87
|
|
|
29
|
-
//
|
|
30
|
-
|
|
88
|
+
// ── Hooks from plugin.json ──
|
|
89
|
+
try {
|
|
90
|
+
const manifestPath = join(plugin.path, 'plugin.json')
|
|
91
|
+
if (existsSync(manifestPath)) {
|
|
92
|
+
const manifest = JSON.parse(readFileSync(manifestPath, 'utf-8'))
|
|
93
|
+
if (manifest.hooks && Array.isArray(manifest.hooks)) {
|
|
94
|
+
for (const hook of manifest.hooks as HookDefinition[]) {
|
|
95
|
+
if (hook.event) {
|
|
96
|
+
hookEngine.register(hook)
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
} catch (err) {
|
|
102
|
+
process.stderr.write(`[plugin] Failed to load hooks from "${plugin.name}": ${String(err)}\n`)
|
|
103
|
+
}
|
|
31
104
|
}
|
|
32
|
-
|
|
33
|
-
// Suppress unused parameter warnings — these are wiring stubs for future phases
|
|
34
|
-
void engine
|
|
35
|
-
void skillsLoader
|
|
36
105
|
}
|
package/src/shared/types.ts
CHANGED
|
@@ -147,6 +147,8 @@ export interface SkillDefinition {
|
|
|
147
147
|
disableModelInvocation?: boolean
|
|
148
148
|
/** When true, users can invoke this skill directly via /<name> */
|
|
149
149
|
userInvocable?: boolean
|
|
150
|
+
/** The markdown body content of the skill file (instructions for the AI to follow). */
|
|
151
|
+
body?: string
|
|
150
152
|
}
|
|
151
153
|
|
|
152
154
|
// ── Hook Types ──
|
package/src/skills/loader.ts
CHANGED
|
@@ -98,13 +98,14 @@ export class SkillsLoader {
|
|
|
98
98
|
private tryLoad(path: string, type: 'standard' | 'mipham'): void {
|
|
99
99
|
try {
|
|
100
100
|
const raw = readFileSync(path, 'utf-8')
|
|
101
|
-
const { data } = parseFrontmatter(raw)
|
|
101
|
+
const { data, content } = parseFrontmatter(raw)
|
|
102
102
|
|
|
103
103
|
const skill: SkillDefinition = {
|
|
104
104
|
name: (data.name as string) || this.nameFromPath(path),
|
|
105
105
|
description: (data.description as string) || '',
|
|
106
106
|
version: (data.version as string) || '0.1.0',
|
|
107
107
|
type,
|
|
108
|
+
body: content.trim() || undefined,
|
|
108
109
|
tools: data.tools as SkillDefinition['tools'],
|
|
109
110
|
hooks: data.hooks as SkillDefinition['hooks'],
|
|
110
111
|
prompts: data.prompts as SkillDefinition['prompts'],
|
package/src/tools/agent/skill.ts
CHANGED
|
@@ -64,15 +64,16 @@ export const skillTool: ToolDefinition = {
|
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
// Standard inline execution — return
|
|
67
|
+
// Standard inline execution — return skill body for AI to follow
|
|
68
68
|
const lines: string[] = [
|
|
69
69
|
`── Skill Invoked: ${skill.name} ──`,
|
|
70
70
|
`Type: ${skill.type} | Version: ${skill.version}`,
|
|
71
71
|
skill.description ? `Description: ${skill.description}` : '',
|
|
72
72
|
args ? `Arguments: ${args}` : '',
|
|
73
73
|
'',
|
|
74
|
-
'The AI should now follow
|
|
75
|
-
'
|
|
74
|
+
'The AI should now follow these instructions:',
|
|
75
|
+
'',
|
|
76
|
+
skill.body || '(no instructions body)',
|
|
76
77
|
].filter(Boolean)
|
|
77
78
|
|
|
78
79
|
return { success: true, content: lines.join('\n') }
|
package/src/ui/app.tsx
CHANGED
|
@@ -97,10 +97,14 @@ export function App({
|
|
|
97
97
|
|
|
98
98
|
// Initialize agent registry (one-time, on mount)
|
|
99
99
|
useMemo(() => {
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
100
|
+
// Agent registry may already be initialized in index.tsx (for plugin loading).
|
|
101
|
+
// Only create a fresh one if not already set on the engine.
|
|
102
|
+
if (!engine.getAgentRegistry()) {
|
|
103
|
+
const agentRegistry = new AgentRegistry()
|
|
104
|
+
agentRegistry.loadUserAgents()
|
|
105
|
+
agentRegistry.loadProjectAgents(process.cwd())
|
|
106
|
+
engine.setAgentRegistry(agentRegistry)
|
|
107
|
+
}
|
|
104
108
|
}, [])
|
|
105
109
|
|
|
106
110
|
const mkCtx = useCallback(
|