@miphamai/cli 0.3.0 → 0.5.0
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/bin/mipham.ts +188 -0
- package/dist/mipham +0 -0
- package/package.json +9 -9
- package/skills/mipham/om-artifact.mipham-skill.md +69 -0
- package/skills/mipham/om-model-optimize.mipham-skill.md +9 -6
- package/skills/mipham/om-security.mipham-skill.md +11 -8
- package/skills/standard/doc-generator.SKILL.md +6 -1
- package/skills/standard/github-ops.SKILL.md +12 -7
- package/skills/standard/memory.SKILL.md +1 -0
- package/skills/standard/self-review.SKILL.md +1 -0
- package/skills/standard/superpower.SKILL.md +7 -7
- package/skills/standard/web-search.SKILL.md +3 -0
- package/src/agent/agent-context.ts +56 -0
- package/src/agent/agent-registry.ts +134 -0
- package/src/agent/sub-agent.ts +73 -89
- package/src/agent/types.ts +39 -0
- package/src/agent-view/agent-view-manager.ts +217 -0
- package/src/agent-view/dashboard.tsx +218 -0
- package/src/agent-view/session-peek.tsx +72 -0
- package/src/agent-view/session-row.tsx +70 -0
- package/src/artifacts/manifest.ts +101 -0
- package/src/artifacts/server.ts +374 -0
- package/src/artifacts/versioning.ts +127 -0
- package/src/core/context-compact.ts +50 -0
- package/src/core/context-drain.ts +54 -0
- package/src/core/context-microcompact.ts +132 -0
- package/src/core/context-snip.ts +74 -0
- package/src/core/context-token.ts +108 -0
- package/src/core/context.ts +169 -0
- package/src/core/engine.ts +207 -58
- package/src/core/hooks-config.ts +52 -0
- package/src/core/hooks-executor.ts +113 -0
- package/src/core/hooks.ts +90 -30
- package/src/core/instructions.ts +12 -1
- package/src/core/memory/memory-loader.ts +23 -0
- package/src/core/memory/memory-manager.ts +192 -0
- package/src/core/memory/memory-writer.ts +72 -0
- package/src/core/permission-config.ts +34 -0
- package/src/core/permission-rules.ts +66 -0
- package/src/core/permission.ts +224 -34
- package/src/index.tsx +30 -2
- package/src/mcp/transport.ts +42 -5
- package/src/plugin/plugin-loader.ts +36 -0
- package/src/plugin/plugin-manager.ts +125 -0
- package/src/plugin/plugin-validator.ts +41 -0
- package/src/providers/fetch-utils.ts +1 -3
- package/src/providers/openai-compat.ts +2 -11
- package/src/shared/constants.ts +8 -1
- package/src/shared/types.ts +82 -2
- package/src/skills/fork-executor.ts +40 -0
- package/src/skills/loader.ts +48 -2
- package/src/tools/agent/agent.ts +20 -11
- package/src/tools/agent/skill.ts +32 -2
- package/src/tools/artifact/artifact.ts +144 -0
- package/src/tools/computer/app-launcher.ts +39 -0
- package/src/tools/computer/browser.ts +48 -0
- package/src/tools/computer/computer-use.ts +88 -0
- package/src/tools/computer/playwright.d.ts +7 -0
- package/src/tools/computer/screenshot.ts +57 -0
- package/src/tools/index.ts +6 -0
- package/src/ui/app.tsx +20 -7
- package/src/ui/chat.tsx +9 -5
- package/src/ui/commands.ts +185 -40
- package/src/ui/input.tsx +203 -27
- package/src/ui/picker.tsx +2 -5
- package/src/ui/vim-motions.ts +96 -0
- package/src/workflow/budget.ts +33 -0
- package/src/workflow/journal.ts +105 -0
- package/src/workflow/primitives/agent.ts +51 -0
- package/src/workflow/primitives/parallel.ts +8 -0
- package/src/workflow/primitives/phase.ts +19 -0
- package/src/workflow/primitives/pipeline.ts +24 -0
- package/src/workflow/runtime.ts +87 -0
- package/src/workflow/sandbox.ts +75 -0
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { execSync } from 'node:child_process'
|
|
2
|
+
import { readFileSync, unlinkSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { tmpdir } from 'node:os'
|
|
4
|
+
import { join } from 'node:path'
|
|
5
|
+
|
|
6
|
+
export async function takeScreenshot(): Promise<{
|
|
7
|
+
success: boolean
|
|
8
|
+
data?: string
|
|
9
|
+
error?: string
|
|
10
|
+
}> {
|
|
11
|
+
const tmpPath = join(tmpdir(), `mipham-screenshot-${Date.now()}.png`)
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
const platform = process.platform
|
|
15
|
+
if (platform === 'darwin') {
|
|
16
|
+
execSync(`screencapture -x ${tmpPath}`, { timeout: 10000 })
|
|
17
|
+
} else if (platform === 'linux') {
|
|
18
|
+
execSync(`import -window root ${tmpPath}`, { timeout: 10000 })
|
|
19
|
+
} else if (platform === 'win32') {
|
|
20
|
+
// PowerShell script to capture primary screen and save as PNG
|
|
21
|
+
const psScript = `
|
|
22
|
+
Add-Type -AssemblyName System.Windows.Forms
|
|
23
|
+
Add-Type -AssemblyName System.Drawing
|
|
24
|
+
$screen = [System.Windows.Forms.Screen]::PrimaryScreen
|
|
25
|
+
$bitmap = New-Object System.Drawing.Bitmap($screen.Bounds.Width, $screen.Bounds.Height)
|
|
26
|
+
$graphics = [System.Drawing.Graphics]::FromImage($bitmap)
|
|
27
|
+
$graphics.CopyFromScreen($screen.Bounds.X, $screen.Bounds.Y, 0, 0, $bitmap.Size)
|
|
28
|
+
$bitmap.Save('${tmpPath.replace(/\\/g, '\\\\')}', [System.Drawing.Imaging.ImageFormat]::Png)
|
|
29
|
+
$graphics.Dispose()
|
|
30
|
+
$bitmap.Dispose()
|
|
31
|
+
`.trim()
|
|
32
|
+
const psPath = join(tmpdir(), `mipham-sc-${Date.now()}.ps1`)
|
|
33
|
+
writeFileSync(psPath, psScript, 'utf-8')
|
|
34
|
+
execSync(`powershell -ExecutionPolicy Bypass -File "${psPath}"`, { timeout: 15000 })
|
|
35
|
+
try {
|
|
36
|
+
unlinkSync(psPath)
|
|
37
|
+
} catch {
|
|
38
|
+
// ignore cleanup failures
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const data = readFileSync(tmpPath, 'base64')
|
|
43
|
+
try {
|
|
44
|
+
unlinkSync(tmpPath)
|
|
45
|
+
} catch {
|
|
46
|
+
// ignore cleanup failures
|
|
47
|
+
}
|
|
48
|
+
return { success: true, data: `data:image/png;base64,${data}` }
|
|
49
|
+
} catch (err) {
|
|
50
|
+
try {
|
|
51
|
+
unlinkSync(tmpPath)
|
|
52
|
+
} catch {
|
|
53
|
+
// ignore cleanup failures
|
|
54
|
+
}
|
|
55
|
+
return { success: false, error: `Screenshot failed: ${String(err)}` }
|
|
56
|
+
}
|
|
57
|
+
}
|
package/src/tools/index.ts
CHANGED
|
@@ -15,6 +15,8 @@ import { webFetchTool } from './network/web-fetch'
|
|
|
15
15
|
import { webSearchTool } from './network/web-search'
|
|
16
16
|
import { configTool } from './system/config'
|
|
17
17
|
import { mcpTool } from './system/mcp'
|
|
18
|
+
import { artifactTool } from './artifact/artifact'
|
|
19
|
+
import { computerUseTool } from './computer/computer-use'
|
|
18
20
|
|
|
19
21
|
/**
|
|
20
22
|
* Validate tool parameters against the tool's JSON Schema definition.
|
|
@@ -116,6 +118,10 @@ export function createToolRegistry(): Map<string, ToolDefinition> {
|
|
|
116
118
|
// System tools
|
|
117
119
|
withValidation(configTool),
|
|
118
120
|
withValidation(mcpTool),
|
|
121
|
+
// Artifact tools
|
|
122
|
+
withValidation(artifactTool),
|
|
123
|
+
// Computer Use tools
|
|
124
|
+
withValidation(computerUseTool),
|
|
119
125
|
]
|
|
120
126
|
|
|
121
127
|
const map = new Map<string, ToolDefinition>()
|
package/src/ui/app.tsx
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import React, { useState, useCallback, useRef } from 'react'
|
|
1
|
+
import React, { useState, useCallback, useRef, useMemo } from 'react'
|
|
2
2
|
import { Box, Text, useInput } from 'ink'
|
|
3
3
|
import type { QueryEngine } from '../core/engine'
|
|
4
4
|
import type { MiphamConfig } from '../shared/index.ts'
|
|
5
5
|
import type { SkillsLoader } from '../skills/loader'
|
|
6
|
+
import { AgentRegistry } from '../agent/agent-registry'
|
|
6
7
|
import { ChatPanel } from './chat'
|
|
7
8
|
import { InputBar } from './input'
|
|
8
9
|
import { ModelPicker } from './picker'
|
|
@@ -58,7 +59,7 @@ export function App({
|
|
|
58
59
|
config,
|
|
59
60
|
initialProvider,
|
|
60
61
|
initialModel,
|
|
61
|
-
lang,
|
|
62
|
+
lang: _lang,
|
|
62
63
|
skillsLoader,
|
|
63
64
|
}: AppProps) {
|
|
64
65
|
const [messages, setMessages] = useState<ChatMessage[]>([])
|
|
@@ -78,13 +79,24 @@ export function App({
|
|
|
78
79
|
|
|
79
80
|
// Agent elapsed timer
|
|
80
81
|
React.useEffect(() => {
|
|
81
|
-
if (!agentProgress) {
|
|
82
|
+
if (!agentProgress) {
|
|
83
|
+
setAgentElapsed(0)
|
|
84
|
+
return
|
|
85
|
+
}
|
|
82
86
|
const interval = setInterval(() => {
|
|
83
87
|
setAgentElapsed(Math.floor((Date.now() - agentProgress.startTime) / 1000))
|
|
84
88
|
}, 1000)
|
|
85
89
|
return () => clearInterval(interval)
|
|
86
90
|
}, [agentProgress])
|
|
87
91
|
|
|
92
|
+
// Initialize agent registry (one-time, on mount)
|
|
93
|
+
useMemo(() => {
|
|
94
|
+
const agentRegistry = new AgentRegistry()
|
|
95
|
+
agentRegistry.loadUserAgents()
|
|
96
|
+
agentRegistry.loadProjectAgents(process.cwd())
|
|
97
|
+
engine.setAgentRegistry(agentRegistry)
|
|
98
|
+
}, [])
|
|
99
|
+
|
|
88
100
|
const mkCtx = useCallback(
|
|
89
101
|
(): CommandContext => ({
|
|
90
102
|
engine,
|
|
@@ -216,8 +228,10 @@ export function App({
|
|
|
216
228
|
if (isAgent) {
|
|
217
229
|
setAgentProgress({
|
|
218
230
|
name: (chunk.toolUse.input.subagent_type as string) || 'General-purpose',
|
|
219
|
-
description:
|
|
220
|
-
(chunk.toolUse.input.
|
|
231
|
+
description:
|
|
232
|
+
(chunk.toolUse.input.description as string) ||
|
|
233
|
+
(chunk.toolUse.input.prompt as string) ||
|
|
234
|
+
'',
|
|
221
235
|
startTime: Date.now(),
|
|
222
236
|
})
|
|
223
237
|
}
|
|
@@ -379,8 +393,7 @@ export function App({
|
|
|
379
393
|
{agentProgress && (
|
|
380
394
|
<Box flexDirection="column" marginY={1}>
|
|
381
395
|
<Text color="cyan" bold>
|
|
382
|
-
⏺ {agentProgress.name}{
|
|
383
|
-
<Text color="yellow">{agentElapsed}s</Text>
|
|
396
|
+
⏺ {agentProgress.name} <Text color="yellow">{agentElapsed}s</Text>
|
|
384
397
|
</Text>
|
|
385
398
|
<Text dimColor>
|
|
386
399
|
{agentProgress.description.length > 100
|
package/src/ui/chat.tsx
CHANGED
|
@@ -39,8 +39,8 @@ export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
|
|
|
39
39
|
</Box>
|
|
40
40
|
<Box marginTop={1}>
|
|
41
41
|
<Text dimColor>
|
|
42
|
-
Tip: Use <Text color="yellow">/clear</Text> to start fresh when switching topics
|
|
43
|
-
|
|
42
|
+
Tip: Use <Text color="yellow">/clear</Text> to start fresh when switching topics and
|
|
43
|
+
free up context
|
|
44
44
|
</Text>
|
|
45
45
|
</Box>
|
|
46
46
|
</Box>
|
|
@@ -50,8 +50,7 @@ export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
|
|
|
50
50
|
{msg.toolMeta ? (
|
|
51
51
|
<Box flexDirection="column">
|
|
52
52
|
<Text color="yellow">
|
|
53
|
-
{msg.toolMeta.collapsed ? '⏺' : '⏺ ▼'}{
|
|
54
|
-
{msg.toolMeta.name}
|
|
53
|
+
{msg.toolMeta.collapsed ? '⏺' : '⏺ ▼'} {msg.toolMeta.name}
|
|
55
54
|
</Text>
|
|
56
55
|
<Text dimColor>{msg.content}</Text>
|
|
57
56
|
</Box>
|
|
@@ -61,7 +60,12 @@ export function ChatPanel({ messages, focusMode }: ChatPanelProps) {
|
|
|
61
60
|
bold
|
|
62
61
|
color={msg.role === 'user' ? 'green' : msg.role === 'system' ? 'yellow' : 'blue'}
|
|
63
62
|
>
|
|
64
|
-
{msg.role === 'user'
|
|
63
|
+
{msg.role === 'user'
|
|
64
|
+
? '▸ You'
|
|
65
|
+
: msg.role === 'assistant'
|
|
66
|
+
? 'Mipham Code'
|
|
67
|
+
: '⚠ System'}
|
|
68
|
+
:
|
|
65
69
|
</Text>
|
|
66
70
|
<Text>{msg.content}</Text>
|
|
67
71
|
</>
|
package/src/ui/commands.ts
CHANGED
|
@@ -8,14 +8,7 @@ import type { QueryEngine } from '../core/engine'
|
|
|
8
8
|
import type { MiphamConfig } from '../shared/index.ts'
|
|
9
9
|
import type { SkillsLoader } from '../skills/loader'
|
|
10
10
|
import { McpClient } from '../mcp/client'
|
|
11
|
-
import {
|
|
12
|
-
PACKAGE_NAME,
|
|
13
|
-
NPM_INSTALL_COMMAND,
|
|
14
|
-
NPM_UPDATE_COMMAND,
|
|
15
|
-
NPM_URL,
|
|
16
|
-
PRODUCT_URL_INTERNATIONAL,
|
|
17
|
-
PRODUCT_URL_CHINA,
|
|
18
|
-
} from '@mipham/shared'
|
|
11
|
+
import { NPM_INSTALL_COMMAND, NPM_UPDATE_COMMAND } from '@mipham/shared'
|
|
19
12
|
|
|
20
13
|
export interface CommandContext {
|
|
21
14
|
engine: QueryEngine
|
|
@@ -133,7 +126,10 @@ const helpCmd: CommandHandler = (_ctx) => ({
|
|
|
133
126
|
/login Show API key status
|
|
134
127
|
/logout Clear credentials guide
|
|
135
128
|
/feedback Send feedback
|
|
136
|
-
|
|
129
|
+
|
|
130
|
+
── Agents ──────────────────────────
|
|
131
|
+
/agents Agent view dashboard
|
|
132
|
+
/bg <prompt> Run a background agent task
|
|
137
133
|
|
|
138
134
|
Type /exit or Esc to quit.
|
|
139
135
|
`,
|
|
@@ -421,12 +417,12 @@ const renameCmd: CommandHandler = (ctx, args) => {
|
|
|
421
417
|
const goalCmd: CommandHandler = (ctx, args) => {
|
|
422
418
|
const goal = args.join(' ')
|
|
423
419
|
if (!goal.trim()) {
|
|
424
|
-
const c = ctx.engine.getContext()
|
|
425
420
|
return {
|
|
426
421
|
content: `Usage: /goal <statement>\n\nSet a session-level completion condition. Mipham Code will track progress toward this goal.\n\nExample: /goal Fix all TypeScript errors and make tests pass`,
|
|
427
422
|
}
|
|
428
423
|
}
|
|
429
424
|
ctx.setGoal(goal.trim())
|
|
425
|
+
ctx.engine.setGoal(goal.trim())
|
|
430
426
|
return {
|
|
431
427
|
content: `✓ Goal set: "${goal.trim()}"\n\nUse /status to view progress. Type /goal without arguments to clear.`,
|
|
432
428
|
}
|
|
@@ -867,7 +863,7 @@ const doctorCmd: CommandHandler = async (ctx) => {
|
|
|
867
863
|
// ═══════════════════════════════════════════════════════════════
|
|
868
864
|
|
|
869
865
|
const exportCmd: CommandHandler = async (ctx) => {
|
|
870
|
-
const { writeFileSync
|
|
866
|
+
const { writeFileSync } = await import('node:fs')
|
|
871
867
|
const { join } = await import('node:path')
|
|
872
868
|
|
|
873
869
|
const cwd = process.cwd()
|
|
@@ -2086,47 +2082,192 @@ const feedbackCmd: CommandHandler = (ctx, args) => {
|
|
|
2086
2082
|
// Phase 4 — Agent Management
|
|
2087
2083
|
// ═══════════════════════════════════════════════════════════════
|
|
2088
2084
|
|
|
2089
|
-
const agentsCmd: CommandHandler = () =>
|
|
2090
|
-
|
|
2085
|
+
const agentsCmd: CommandHandler = (ctx) => {
|
|
2086
|
+
const agentViewManager = ctx.engine.getAgentViewManager?.()
|
|
2087
|
+
if (!agentViewManager) {
|
|
2088
|
+
return { content: 'Agent View dashboard is initializing...' }
|
|
2089
|
+
}
|
|
2090
|
+
const counts = agentViewManager.countByStatus()
|
|
2091
|
+
const total = counts['needs-input'] + counts.working + counts.completed + counts.failed
|
|
2091
2092
|
|
|
2092
|
-
|
|
2093
|
+
if (total === 0) {
|
|
2094
|
+
return {
|
|
2095
|
+
content: `── Agent View ──
|
|
2093
2096
|
|
|
2094
|
-
|
|
2095
|
-
│ Agent │ Category │ Permission │ Parameters │
|
|
2096
|
-
├──────────┼──────────┼────────────┼────────────────────────────────┤
|
|
2097
|
-
│ Agent │ agent │ ask │ description*, prompt*, │
|
|
2098
|
-
│ │ │ │ subagent_type (optional) │
|
|
2099
|
-
│ Skill │ agent │ auto │ skill*, args (optional) │
|
|
2100
|
-
│ Plan │ agent │ auto │ (no required params) │
|
|
2101
|
-
│ Memory │ agent │ auto │ action* (read|write|list), │
|
|
2102
|
-
│ │ │ │ name, content │
|
|
2103
|
-
└──────────┴──────────┴────────────┴────────────────────────────────┘
|
|
2097
|
+
No background agents running.
|
|
2104
2098
|
|
|
2105
|
-
|
|
2099
|
+
Use /bg <prompt> to spawn a background agent session.
|
|
2106
2100
|
|
|
2107
|
-
|
|
2108
|
-
|
|
2101
|
+
Example:
|
|
2102
|
+
/bg Fix all TypeScript errors in the project
|
|
2103
|
+
/bg Review the auth module for security issues
|
|
2109
2104
|
|
|
2110
|
-
|
|
2111
|
-
|
|
2105
|
+
Or use the Agent tool in a conversation to launch a sub-agent.`,
|
|
2106
|
+
}
|
|
2107
|
+
}
|
|
2112
2108
|
|
|
2113
|
-
|
|
2114
|
-
|
|
2109
|
+
const lines: string[] = [
|
|
2110
|
+
'── Agent View ──',
|
|
2111
|
+
'',
|
|
2112
|
+
` ${total} session(s):`,
|
|
2113
|
+
` 🟡 Needs input: ${counts['needs-input']}`,
|
|
2114
|
+
` 🔵 Working: ${counts.working}`,
|
|
2115
|
+
` 🟢 Completed: ${counts.completed}`,
|
|
2116
|
+
` 🔴 Failed: ${counts.failed}`,
|
|
2117
|
+
'',
|
|
2118
|
+
'Sessions:',
|
|
2119
|
+
]
|
|
2115
2120
|
|
|
2116
|
-
|
|
2117
|
-
|
|
2121
|
+
const all = agentViewManager.list()
|
|
2122
|
+
for (const s of all.slice(0, 10)) {
|
|
2123
|
+
const statusIcon: Record<string, string> = {
|
|
2124
|
+
'needs-input': '🟡',
|
|
2125
|
+
working: '🔵',
|
|
2126
|
+
completed: '🟢',
|
|
2127
|
+
failed: '🔴',
|
|
2128
|
+
}
|
|
2129
|
+
const elapsed =
|
|
2130
|
+
s.elapsedMs < 1000
|
|
2131
|
+
? '<1s'
|
|
2132
|
+
: s.elapsedMs < 60000
|
|
2133
|
+
? `${Math.floor(s.elapsedMs / 1000)}s`
|
|
2134
|
+
: `${Math.floor(s.elapsedMs / 60000)}m`
|
|
2135
|
+
lines.push(
|
|
2136
|
+
` ${statusIcon[s.status] || '⚪'} ${s.id} · ${s.provider}/${s.model} · ${elapsed} · ${s.task.slice(0, 50)}`,
|
|
2137
|
+
)
|
|
2138
|
+
}
|
|
2118
2139
|
|
|
2119
|
-
|
|
2140
|
+
if (total > 10) {
|
|
2141
|
+
lines.push(` ... and ${total - 10} more`)
|
|
2142
|
+
}
|
|
2120
2143
|
|
|
2121
|
-
|
|
2144
|
+
lines.push('', 'Run `mipham agents` for the full interactive dashboard with j/k navigation.')
|
|
2122
2145
|
|
|
2123
|
-
|
|
2124
|
-
|
|
2146
|
+
return { content: lines.join('\n') }
|
|
2147
|
+
}
|
|
2125
2148
|
|
|
2126
|
-
|
|
2149
|
+
const bgCmd: CommandHandler = (ctx, args) => {
|
|
2150
|
+
const prompt = args.join(' ')
|
|
2151
|
+
if (!prompt.trim()) {
|
|
2152
|
+
return {
|
|
2153
|
+
content: `Usage: /bg <prompt>
|
|
2127
2154
|
|
|
2128
|
-
|
|
2129
|
-
|
|
2155
|
+
Spawn a background agent to work on a task while you continue chatting.
|
|
2156
|
+
|
|
2157
|
+
Examples:
|
|
2158
|
+
/bg Run the full test suite and report failures
|
|
2159
|
+
/bg Audit the src/ directory for security issues
|
|
2160
|
+
/bg Generate API documentation from JSDoc comments
|
|
2161
|
+
|
|
2162
|
+
Background agents appear in the Agent View dashboard (/agents).`,
|
|
2163
|
+
}
|
|
2164
|
+
}
|
|
2165
|
+
|
|
2166
|
+
const agentViewManager = ctx.engine.getAgentViewManager?.()
|
|
2167
|
+
if (!agentViewManager) {
|
|
2168
|
+
return { content: 'Agent View manager is initializing...' }
|
|
2169
|
+
}
|
|
2170
|
+
|
|
2171
|
+
const session = agentViewManager.create(prompt, prompt, {
|
|
2172
|
+
provider: ctx.providerId,
|
|
2173
|
+
model: ctx.modelId,
|
|
2174
|
+
})
|
|
2175
|
+
|
|
2176
|
+
agentViewManager.addMessage(session.id, { role: 'user', content: prompt })
|
|
2177
|
+
agentViewManager.updateStatus(session.id, 'working')
|
|
2178
|
+
|
|
2179
|
+
return {
|
|
2180
|
+
content: `✓ Background agent spawned: ${session.id}
|
|
2181
|
+
|
|
2182
|
+
Task: ${session.task.slice(0, 80)}
|
|
2183
|
+
Provider: ${session.provider} / ${session.model}
|
|
2184
|
+
Status: working
|
|
2185
|
+
|
|
2186
|
+
Use /agents to view all background sessions.
|
|
2187
|
+
Run \`mipham agents\` for the interactive dashboard.`,
|
|
2188
|
+
}
|
|
2189
|
+
}
|
|
2190
|
+
|
|
2191
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2192
|
+
// Artifacts
|
|
2193
|
+
// ═══════════════════════════════════════════════════════════════
|
|
2194
|
+
|
|
2195
|
+
async function openBrowser(url: string): Promise<void> {
|
|
2196
|
+
const cmd =
|
|
2197
|
+
process.platform === 'darwin'
|
|
2198
|
+
? `open "${url}"`
|
|
2199
|
+
: process.platform === 'win32'
|
|
2200
|
+
? `start "" "${url}"`
|
|
2201
|
+
: `xdg-open "${url}"`
|
|
2202
|
+
const { exec } = await import('node:child_process')
|
|
2203
|
+
exec(cmd, () => {
|
|
2204
|
+
/* fire-and-forget */
|
|
2205
|
+
})
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
const artifactOpenCmd: CommandHandler = async (ctx, args) => {
|
|
2209
|
+
const name = args[0]
|
|
2210
|
+
if (!name) {
|
|
2211
|
+
return { content: 'Usage: /artifact open <name>\nExample: /artifact open dashboard' }
|
|
2212
|
+
}
|
|
2213
|
+
|
|
2214
|
+
const sessionId = 'session-1' // default session
|
|
2215
|
+
const port = 9876
|
|
2216
|
+
const ext = name.endsWith('.svg') ? '' : '.html'
|
|
2217
|
+
const url = `http://localhost:${port}/${sessionId}/${name}${ext}`
|
|
2218
|
+
|
|
2219
|
+
try {
|
|
2220
|
+
await openBrowser(url)
|
|
2221
|
+
return { content: `✓ Opening artifact "${name}" in browser...\n ${url}` }
|
|
2222
|
+
} catch (err) {
|
|
2223
|
+
return { content: `✗ Failed to open browser: ${String(err)}\n URL: ${url}` }
|
|
2224
|
+
}
|
|
2225
|
+
}
|
|
2226
|
+
|
|
2227
|
+
const artifactListCmd: CommandHandler = async (_ctx, _args) => {
|
|
2228
|
+
const { getSessionArtifacts } = await import('../artifacts/manifest')
|
|
2229
|
+
const { join } = await import('node:path')
|
|
2230
|
+
const { ARTIFACTS_DIR } = await import('../shared/constants')
|
|
2231
|
+
|
|
2232
|
+
const dir = join(process.cwd(), ARTIFACTS_DIR)
|
|
2233
|
+
const entries = getSessionArtifacts(dir, 'session-1')
|
|
2234
|
+
|
|
2235
|
+
if (entries.length === 0) {
|
|
2236
|
+
return {
|
|
2237
|
+
content: 'No artifacts created yet. Ask the AI to generate one with the Artifact tool.',
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
|
|
2241
|
+
const lines = ['── Artifacts ──', '']
|
|
2242
|
+
for (const e of entries) {
|
|
2243
|
+
const ver = e.versions && e.versions.length > 1 ? ` v${e.versions.length}` : ''
|
|
2244
|
+
lines.push(
|
|
2245
|
+
` ${e.name.padEnd(24)} ${(e.type + ver).padEnd(8)} ${(e.size / 1024).toFixed(1).padStart(8)}KB ${e.createdAt.slice(0, 16).replace('T', ' ')}`,
|
|
2246
|
+
)
|
|
2247
|
+
}
|
|
2248
|
+
lines.push('', ` ${entries.length} artifact(s) — /artifact open <name> to view`)
|
|
2249
|
+
lines.push(` Gallery: http://localhost:9876`)
|
|
2250
|
+
|
|
2251
|
+
return { content: lines.join('\n') }
|
|
2252
|
+
}
|
|
2253
|
+
|
|
2254
|
+
const artifactServerCmd: CommandHandler = async (_ctx, _args) => {
|
|
2255
|
+
const url = 'http://localhost:9876'
|
|
2256
|
+
return {
|
|
2257
|
+
content: `── Artifact Server ──\n\n URL: ${url}\n Port: 9876\n\nArtifacts are served locally. No external network access.`,
|
|
2258
|
+
}
|
|
2259
|
+
}
|
|
2260
|
+
|
|
2261
|
+
const artifactBaseCmd: CommandHandler = (ctx, args) => {
|
|
2262
|
+
const sub = args[0]
|
|
2263
|
+
if (sub === 'open') return artifactOpenCmd(ctx, args.slice(1))
|
|
2264
|
+
if (sub === 'list') return artifactListCmd(ctx, [])
|
|
2265
|
+
if (sub === 'server') return artifactServerCmd(ctx, [])
|
|
2266
|
+
return {
|
|
2267
|
+
content:
|
|
2268
|
+
'Usage: /artifact <open|list|server> [name]\n\n /artifact open <name> Open artifact in browser\n /artifact list List all artifacts\n /artifact server Show server status',
|
|
2269
|
+
}
|
|
2270
|
+
}
|
|
2130
2271
|
|
|
2131
2272
|
// ═══════════════════════════════════════════════════════════════
|
|
2132
2273
|
// Remaining low-priority stubs (backend not yet available)
|
|
@@ -2213,6 +2354,10 @@ registry.set('/login', loginCmd)
|
|
|
2213
2354
|
registry.set('/logout', logoutCmd)
|
|
2214
2355
|
registry.set('/feedback', feedbackCmd)
|
|
2215
2356
|
registry.set('/agents', agentsCmd)
|
|
2357
|
+
registry.set('/bg', bgCmd)
|
|
2358
|
+
|
|
2359
|
+
// Artifacts
|
|
2360
|
+
registry.set('/artifact', artifactBaseCmd)
|
|
2216
2361
|
|
|
2217
2362
|
// Lower-priority semi-stubs (WIP, backend pending)
|
|
2218
2363
|
registry.set('/branch', branchCmd)
|