Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions packages/mayfly/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@
"commander": "^15.0.0",
"semver": "7.8.5",
"simple-ascii-chart": "6.0.0",
"yaml": "2.9.0",
"zod": "^4.4.3"
},
"peerDependencies": {
Expand Down
8 changes: 7 additions & 1 deletion packages/mayfly/src/interaction/commands-plugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@
* catalog, `/preset` over the agent-preset roster) lives in
* `./tools-commands.ts` and `./preset-commands.ts`; and `/skills` (the
* `#` pipeline's read-only listing) lives in `./skills-command.ts`; the
* settings panel (`/settings`) lives in `./settings-command.ts`.
* settings panel (`/settings`) lives in `./settings-command.ts`; and
* `/plugin` (the marketplace browser over the dsh-plugins index) lives in
* `./plugin-commands.ts`.
* Registrations are
* effect-bound, so unloading the fiber removes them. Only `commands` is
* injected: the overlay commands read the Mayfly display services through
Expand Down Expand Up @@ -53,6 +55,7 @@ import type { HelpSection } from './help.ts'
import { HelpOverlay } from './help.ts'
import { registerMcpCommands } from './mcp-commands.ts'
import { registerModelCommands } from './model-commands.ts'
import { registerPluginCommand } from './plugin-commands.ts'
import { registerPresetCommands } from './preset-commands.ts'
import { registerSessionCommands } from './session-commands.ts'
import { registerExportCommands } from './session-export.ts'
Expand Down Expand Up @@ -469,6 +472,8 @@ export function apply(ctx: Context, config: Config = {}): void {
const trace = registerTraceCommand(ctx)
// `/update` is the crash-safe, preflighted profile swap.
const update = registerUpdateCommand(ctx)
// `/plugin` browses the marketplace index and installs or removes plugins.
const pluginMarket = registerPluginCommand(ctx)
const settings = registerSettingsCommand(ctx)
return () => {
quit()
Expand All @@ -491,6 +496,7 @@ export function apply(ctx: Context, config: Config = {}): void {
mcpBrowser()
trace()
update()
pluginMarket()
settings()
}
})
Expand Down
23 changes: 21 additions & 2 deletions packages/mayfly/src/interaction/frontend-panel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@ export interface FrontendPanelOptions {
readonly t?: MayflyTranslate
readonly contextHints?: () => readonly CanonicalContextHint[]
readonly showSelectedVariantInFooter?: boolean
/** Group selected on first render; later user navigation remains authoritative. */
readonly initialGroup?: string
}

/** Canonical panel controller preserving the former panel interaction set. */
Expand All @@ -88,9 +90,11 @@ export class CanonicalDocumentController implements MayflyFocusable {
private filterEditing = false
private group = 0
private groupId: string | undefined
private initialGroup: string | undefined
private readonly selectedVariants = new Map<string, string>()

constructor(private readonly options: FrontendPanelOptions) {
this.initialGroup = options.initialGroup
this.adapter = new CanonicalPanelAdapter({
components: options.components,
theme: options.theme,
Expand Down Expand Up @@ -119,7 +123,12 @@ export class CanonicalDocumentController implements MayflyFocusable {
return
}
if (!model.filterable && (data === 'q' || data === 'Q')) { this.cancel(); return }
const unhandled = this.options.onUnhandledInput?.(data, this.resolveSelectedId(model))
if (model.filterable === true && !this.filterEditing && data === '/') {
this.filterEditing = true
this.adapter.invalidate()
return
}
const unhandled = this.filterEditing ? undefined : this.options.onUnhandledInput?.(data, this.resolveSelectedId(model))
if (unhandled !== undefined) { void this.options.onAction(unhandled); return }
if (model.variantNavigation === 'inline' && (data === KEY_LEFT || data === KEY_RIGHT) && this.listIsActive(model)) {
this.moveVariant(model, data === KEY_LEFT ? -1 : 1)
Expand Down Expand Up @@ -240,7 +249,7 @@ export class CanonicalDocumentController implements MayflyFocusable {
] : []),
...(!hasRows && model.view !== undefined ? [{ id: 'scroll', keys: '↑↓/PgUp/PgDn', label: 'scroll', compact: 'PgUp/PgDn', priority: 90 }] : []),
...(this.filterEditing ? [{ id: 'dismiss', keys: 'Esc', label: 'finish search', priority: 96 }] : []),
...(this.options.contextHints?.() ?? []),
...(this.filterEditing ? [] : this.options.contextHints?.() ?? []),
]
}

Expand Down Expand Up @@ -326,6 +335,16 @@ export class CanonicalDocumentController implements MayflyFocusable {
}

private activeGroup(groups: readonly string[]): string {
if (this.groupId === undefined && this.initialGroup !== undefined) {
const index = groups.indexOf(this.initialGroup)
if (index >= 0) {
this.group = index
this.groupId = this.initialGroup
this.initialGroup = undefined
return this.groupId
}
return groups[this.group]!
}
if (this.groupId !== undefined) {
const index = groups.indexOf(this.groupId)
if (index >= 0) {
Expand Down
42 changes: 42 additions & 0 deletions packages/mayfly/src/interaction/locale.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,48 @@ const zh: Readonly<Record<string, string>> = {
'uninstalled; restart Mayfly to apply': '已移除;重启 Mayfly 后生效',
'installed; restart Mayfly to apply, then run /plugin verify {plugin}': '已安装;重启 Mayfly 后生效,再运行 /plugin verify {plugin}',
'catalog refresh failed: {message}': '插件目录刷新失败:{message}',
// The active /plugin family (the marketplace browser). The block above is
// a superseded earlier draft kept only because removing shipped keys is a
// breaking change for translated builds.
'Browse, install, and remove plugins': '浏览、安装与管理插件',
'Plugin marketplace': '插件市场',
'loading catalog...': '正在加载插件目录…',
'marketplace is offline: {message}': '插件市场离线:{message}',
'refresh failed: {message}': '刷新失败:{message}',
'refreshed {count} entries': '已刷新 {count} 个条目',
'unknown plugin: {id}': '未知插件:{id}',
'installing "{name}"...': '正在安装 "{name}"…',
'removing "{name}"...': '正在移除 "{name}"…',
'install failed: {message}': '安装失败:{message}',
'uninstall failed: {message}': '移除失败:{message}',
'installed; restart Mayfly and start a new session to apply': '已安装;重启 Mayfly 并新建会话后生效',
'removed; restart Mayfly and start a new session to apply': '已移除;重启 Mayfly 并新建会话后生效',
'web-only plugin: it contributes nothing in this terminal frontend': '仅 Web 插件:在终端前端无作用',
'automation-only ACP server owns stdio; install it in a dedicated non-Mayfly profile': '仅自动化使用的 ACP Server 会独占 stdio;请安装到独立的非 Mayfly profile',
'i install · u remove · r refresh': 'i 安装 · u 移除 · r 刷新',
'←→ tabs · ↑↓ select': '←→ 页签 · ↑↓ 选择',
'Not installed': '未安装',
'all marketplace plugins are installed': '所有市场插件均已安装',
'refreshing plugin catalog...': '正在刷新插件目录…',
'checking "{name}" compatibility...': '正在检查 "{name}" 兼容性…',
'rolling back "{name}"...': '正在回滚 "{name}"…',
'no plugins installed': '尚未安装插件',
'Overview': '概览',
'Surfaces': '前端贡献',
'Provides': '提供',
'Tools': '工具',
'Source': '来源',
'Engines': '运行环境',
'Verified': '审核',
'Install command': '安装命令',
'Links': '链接',
'works here': '本终端可用',
'no contribution in this terminal': '本终端无贡献',
'works on dsh Web': 'dsh Web 可用',
'no contribution on dsh Web': 'dsh Web 无贡献',
'update available: {version}': '有新版本:{version}',
'removed from the market': '已从市场移除',
'Uninstall': '卸载',
'Approve {tool}?': '是否批准 {tool}?',
'Question {current} of {total}': '问题 {current}/{total}',
'Other': '其他',
Expand Down
Loading
Loading