@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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@miphamai/cli",
3
- "version": "0.6.4",
3
+ "version": "0.6.6",
4
4
  "description": "Mipham Code — Multi-model open-core intelligent coding terminal by MiphamAI",
5
5
  "keywords": [
6
6
  "ai",
@@ -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
- engine: unknown,
8
- skillsLoader: unknown,
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
- // Load custom agents
26
+ // ── Custom agents ──
12
27
  const agentsDir = join(plugin.path, 'agents')
13
28
  if (existsSync(agentsDir)) {
14
- // → register with AgentRegistry
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
- // Load custom skills
38
+ // ── Custom skills ──
18
39
  const skillsDir = join(plugin.path, 'skills')
19
40
  if (existsSync(skillsDir)) {
20
- // → register with SkillsLoader
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
- // Load MCP servers
50
+ // ── MCP servers ──
24
51
  const mcpDir = join(plugin.path, 'mcp-servers')
25
52
  if (existsSync(mcpDir)) {
26
- // → register MCP configs
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
- // Load hooks
30
- // → parse plugin.json hooks → register with HookEngine
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
  }
@@ -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 ──
@@ -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'],
@@ -64,15 +64,16 @@ export const skillTool: ToolDefinition = {
64
64
  }
65
65
  }
66
66
 
67
- // Standard inline execution — return metadata for AI to follow
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 the instructions from this skill.',
75
- 'Skill content has been loaded into context.',
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
- const agentRegistry = new AgentRegistry()
101
- agentRegistry.loadUserAgents()
102
- agentRegistry.loadProjectAgents(process.cwd())
103
- engine.setAgentRegistry(agentRegistry)
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(