mirror of
https://github.com/wu736139669/hapi.git
synced 2026-08-05 06:24:37 +00:00
Share slash command definitions
This commit is contained in:
@@ -2,58 +2,16 @@ import { access, readdir, readFile } from 'fs/promises';
|
||||
import { dirname, join, resolve } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { parse as parseYaml } from 'yaml';
|
||||
import { getBuiltinSlashCommands, mergeSlashCommands } from '@hapi/protocol/slashCommands';
|
||||
import type { SlashCommand, SlashCommandsResponse } from '@hapi/protocol/apiTypes';
|
||||
|
||||
export interface SlashCommand {
|
||||
name: string;
|
||||
description?: string;
|
||||
source: 'builtin' | 'user' | 'plugin' | 'project';
|
||||
content?: string; // Expanded content for Codex user prompts
|
||||
pluginName?: string; // Name of the plugin that provides this command
|
||||
}
|
||||
export type { SlashCommand } from '@hapi/protocol/apiTypes';
|
||||
|
||||
export interface ListSlashCommandsRequest {
|
||||
agent: string;
|
||||
}
|
||||
|
||||
export interface ListSlashCommandsResponse {
|
||||
success: boolean;
|
||||
commands?: SlashCommand[];
|
||||
error?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in slash commands for each agent type.
|
||||
*/
|
||||
const BUILTIN_COMMANDS: Record<string, SlashCommand[]> = {
|
||||
claude: [
|
||||
{ name: 'clear', description: 'Clear conversation history', source: 'builtin' },
|
||||
{ name: 'compact', description: 'Compact conversation context', source: 'builtin' },
|
||||
{ name: 'context', description: 'Show context information', source: 'builtin' },
|
||||
{ name: 'cost', description: 'Show session cost', source: 'builtin' },
|
||||
{ name: 'plan', description: 'Toggle plan mode', source: 'builtin' },
|
||||
],
|
||||
codex: [
|
||||
{ name: 'clear', description: 'Clear current Codex thread context', source: 'builtin' },
|
||||
{ name: 'compact', description: 'Compact current Codex thread context', source: 'builtin' },
|
||||
{ name: 'goal', description: 'Set, view, pause, resume, or clear a persistent Codex goal', source: 'builtin' },
|
||||
{ name: 'help', description: 'Show supported HAPI Codex slash commands', source: 'builtin' },
|
||||
{ name: 'plan', description: 'Enable plan mode; use /plan off to return to default', source: 'builtin' },
|
||||
{ name: 'default', description: 'Return Codex collaboration mode to default', source: 'builtin' },
|
||||
{ name: 'execute', description: 'Return Codex collaboration mode to default', source: 'builtin' },
|
||||
{ name: 'status', description: 'Show current Codex session config', source: 'builtin' },
|
||||
{ name: 'model', description: 'Show or set Codex model, e.g. /model gpt-5.5', source: 'builtin' },
|
||||
{ name: 'reasoning', description: 'Show or set reasoning effort', source: 'builtin' },
|
||||
{ name: 'effort', description: 'Alias for /reasoning', source: 'builtin' },
|
||||
{ name: 'permissions', description: 'Show or set permission mode', source: 'builtin' },
|
||||
{ name: 'permission', description: 'Alias for /permissions', source: 'builtin' },
|
||||
],
|
||||
gemini: [
|
||||
{ name: 'about', description: 'About Gemini', source: 'builtin' },
|
||||
{ name: 'clear', description: 'Clear conversation', source: 'builtin' },
|
||||
{ name: 'compress', description: 'Compress context', source: 'builtin' },
|
||||
],
|
||||
opencode: [],
|
||||
};
|
||||
export type ListSlashCommandsResponse = SlashCommandsResponse;
|
||||
|
||||
/**
|
||||
* Interface for installed_plugins.json structure
|
||||
@@ -316,7 +274,7 @@ async function scanPluginCommands(agent: string): Promise<SlashCommand[]> {
|
||||
* built-in -> global user -> plugin -> project (project overrides same-name globals).
|
||||
*/
|
||||
export async function listSlashCommands(agent: string, projectDir?: string): Promise<SlashCommand[]> {
|
||||
const builtin = BUILTIN_COMMANDS[agent] ?? [];
|
||||
const builtin = getBuiltinSlashCommands(agent);
|
||||
|
||||
// Scan all command sources in parallel
|
||||
const [user, plugin, project] = await Promise.all([
|
||||
@@ -325,16 +283,5 @@ export async function listSlashCommands(agent: string, projectDir?: string): Pro
|
||||
scanProjectCommands(agent, projectDir),
|
||||
]);
|
||||
|
||||
const allCommands = [...builtin, ...user, ...plugin, ...project];
|
||||
|
||||
// Keep insertion order while allowing latter commands to override prior ones.
|
||||
const commandMap = new Map<string, SlashCommand>();
|
||||
for (const command of allCommands) {
|
||||
if (commandMap.has(command.name)) {
|
||||
commandMap.delete(command.name);
|
||||
}
|
||||
commandMap.set(command.name, command);
|
||||
}
|
||||
|
||||
return Array.from(commandMap.values());
|
||||
return mergeSlashCommands([...builtin, ...user, ...plugin, ...project]);
|
||||
}
|
||||
|
||||
@@ -11,6 +11,7 @@ import type {
|
||||
OpencodeModelsResponse,
|
||||
OpencodeModelSummary,
|
||||
PathExistsResponse,
|
||||
SlashCommandsResponse,
|
||||
UploadFileResponse
|
||||
} from '@hapi/protocol/apiTypes'
|
||||
import type { Server } from 'socket.io'
|
||||
@@ -212,16 +213,8 @@ export class RpcGateway {
|
||||
return await this.sessionRpc(sessionId, 'ripgrep', { args, cwd }) as RpcCommandResponse
|
||||
}
|
||||
|
||||
async listSlashCommands(sessionId: string, agent: string): Promise<{
|
||||
success: boolean
|
||||
commands?: Array<{ name: string; description?: string; source: 'builtin' | 'user' | 'plugin' | 'project' }>
|
||||
error?: string
|
||||
}> {
|
||||
return await this.sessionRpc(sessionId, 'listSlashCommands', { agent }) as {
|
||||
success: boolean
|
||||
commands?: Array<{ name: string; description?: string; source: 'builtin' | 'user' | 'plugin' | 'project' }>
|
||||
error?: string
|
||||
}
|
||||
async listSlashCommands(sessionId: string, agent: string): Promise<SlashCommandsResponse> {
|
||||
return await this.sessionRpc(sessionId, 'listSlashCommands', { agent }) as SlashCommandsResponse
|
||||
}
|
||||
|
||||
async listSkills(sessionId: string): Promise<{
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
*/
|
||||
|
||||
import { isKnownFlavor, type LocalResumeTarget, type ResumableSession } from '@hapi/protocol'
|
||||
import type { SlashCommandsResponse } from '@hapi/protocol/apiTypes'
|
||||
import type { AgentFlavor, CodexCollaborationMode, DecryptedMessage, PermissionMode, Session, SyncEvent } from '@hapi/protocol/types'
|
||||
import { unwrapRoleWrappedRecordEnvelope } from '@hapi/protocol/messages'
|
||||
import type { Server } from 'socket.io'
|
||||
@@ -883,11 +884,7 @@ export class SyncEngine {
|
||||
return await this.rpcGateway.runRipgrep(sessionId, args, cwd)
|
||||
}
|
||||
|
||||
async listSlashCommands(sessionId: string, agent: string): Promise<{
|
||||
success: boolean
|
||||
commands?: Array<{ name: string; description?: string; source: 'builtin' | 'user' | 'plugin' | 'project' }>
|
||||
error?: string
|
||||
}> {
|
||||
async listSlashCommands(sessionId: string, agent: string): Promise<SlashCommandsResponse> {
|
||||
return await this.rpcGateway.listSlashCommands(sessionId, agent)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { getPermissionModesForFlavor, isPermissionModeAllowedForFlavor, supportsModelChange, toSessionSummary } from '@hapi/protocol'
|
||||
import type { SlashCommand } from '@hapi/protocol/apiTypes'
|
||||
import { CodexCollaborationModeSchema, PermissionModeSchema } from '@hapi/protocol/schemas'
|
||||
import { Hono } from 'hono'
|
||||
import { z } from 'zod'
|
||||
@@ -46,15 +47,6 @@ const uploadDeleteSchema = z.object({
|
||||
|
||||
const MAX_UPLOAD_BYTES = 50 * 1024 * 1024
|
||||
|
||||
|
||||
type SlashCommand = {
|
||||
name: string
|
||||
description?: string
|
||||
source: 'builtin' | 'user' | 'plugin' | 'project'
|
||||
content?: string
|
||||
pluginName?: string
|
||||
}
|
||||
|
||||
function commandsFromMetadataSlashCommands(names: readonly string[] | undefined): SlashCommand[] {
|
||||
if (!names?.length) {
|
||||
return []
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
".": "./src/index.ts",
|
||||
"./apiTypes": "./src/apiTypes.ts",
|
||||
"./messages": "./src/messages.ts",
|
||||
"./slashCommands": "./src/slashCommands.ts",
|
||||
"./buildInfo": "./src/buildInfo.ts",
|
||||
"./modes": "./src/modes.ts",
|
||||
"./schemas": "./src/schemas.ts",
|
||||
|
||||
@@ -93,3 +93,17 @@ export type OpencodeModelsResponse = {
|
||||
}
|
||||
|
||||
export type ListOpencodeModelsResponse = OpencodeModelsResponse
|
||||
|
||||
export type SlashCommand = {
|
||||
name: string
|
||||
description?: string
|
||||
source: 'builtin' | 'user' | 'plugin' | 'project'
|
||||
content?: string
|
||||
pluginName?: string
|
||||
}
|
||||
|
||||
export type SlashCommandsResponse = {
|
||||
success: boolean
|
||||
commands?: SlashCommand[]
|
||||
error?: string
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ export * from './modes'
|
||||
export * from './resume'
|
||||
export * from './socket'
|
||||
export * from './sessionSummary'
|
||||
export * from './slashCommands'
|
||||
export * from './utils'
|
||||
export * from './version'
|
||||
export type * from './types'
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
import type { SlashCommand } from './apiTypes'
|
||||
|
||||
export const BUILTIN_SLASH_COMMANDS = {
|
||||
claude: [
|
||||
{ name: 'clear', description: 'Clear conversation history and free up context', source: 'builtin' },
|
||||
{ name: 'compact', description: 'Clear conversation history but keep a summary in context', source: 'builtin' },
|
||||
{ name: 'context', description: 'Visualize current context usage as a colored grid', source: 'builtin' },
|
||||
{ name: 'cost', description: 'Show the total cost and duration of the current session', source: 'builtin' },
|
||||
{ name: 'doctor', description: 'Diagnose and verify your Claude Code installation and settings', source: 'builtin' },
|
||||
{ name: 'plan', description: 'View or open the current session plan', source: 'builtin' },
|
||||
{ name: 'stats', description: 'Show your Claude Code usage statistics and activity', source: 'builtin' },
|
||||
{ name: 'status', description: 'Show Claude Code status including version, model, account, and API connectivity', source: 'builtin' },
|
||||
],
|
||||
codex: [
|
||||
{ name: 'clear', description: 'Clear current Codex thread context', source: 'builtin' },
|
||||
{ name: 'compact', description: 'Compact current Codex thread context', source: 'builtin' },
|
||||
{ name: 'goal', description: 'Set, view, pause, resume, or clear a persistent Codex goal', source: 'builtin' },
|
||||
{ name: 'help', description: 'Show supported HAPI Codex slash commands', source: 'builtin' },
|
||||
{ name: 'plan', description: 'Enable plan mode; use /plan off to return to default', source: 'builtin' },
|
||||
{ name: 'default', description: 'Return Codex collaboration mode to default', source: 'builtin' },
|
||||
{ name: 'execute', description: 'Return Codex collaboration mode to default', source: 'builtin' },
|
||||
{ name: 'status', description: 'Show current Codex session config', source: 'builtin' },
|
||||
{ name: 'model', description: 'Show or set Codex model, e.g. /model gpt-5.5', source: 'builtin' },
|
||||
{ name: 'reasoning', description: 'Show or set reasoning effort', source: 'builtin' },
|
||||
{ name: 'effort', description: 'Alias for /reasoning', source: 'builtin' },
|
||||
{ name: 'permissions', description: 'Show or set permission mode', source: 'builtin' },
|
||||
{ name: 'permission', description: 'Alias for /permissions', source: 'builtin' },
|
||||
],
|
||||
gemini: [
|
||||
{ name: 'about', description: 'Show version info', source: 'builtin' },
|
||||
{ name: 'clear', description: 'Clear the screen and conversation history', source: 'builtin' },
|
||||
{ name: 'compress', description: 'Compress the context by replacing it with a summary', source: 'builtin' },
|
||||
{ name: 'stats', description: 'Check session stats', source: 'builtin' },
|
||||
],
|
||||
opencode: [],
|
||||
cursor: [],
|
||||
} as const satisfies Record<string, readonly SlashCommand[]>
|
||||
|
||||
export function getBuiltinSlashCommands(agent: string): SlashCommand[] {
|
||||
const commands = BUILTIN_SLASH_COMMANDS[agent as keyof typeof BUILTIN_SLASH_COMMANDS]
|
||||
?? BUILTIN_SLASH_COMMANDS.claude
|
||||
return commands.map((command) => ({ ...command }))
|
||||
}
|
||||
|
||||
export function mergeSlashCommands(commands: readonly SlashCommand[]): SlashCommand[] {
|
||||
const commandMap = new Map<string, SlashCommand>()
|
||||
for (const command of commands) {
|
||||
const key = command.name.toLowerCase()
|
||||
if (commandMap.has(key)) {
|
||||
commandMap.delete(key)
|
||||
}
|
||||
commandMap.set(key, command)
|
||||
}
|
||||
return Array.from(commandMap.values())
|
||||
}
|
||||
@@ -1,39 +1,8 @@
|
||||
import type { SlashCommand } from '@/types/api'
|
||||
|
||||
const BUILTIN_COMMANDS: Record<string, SlashCommand[]> = {
|
||||
claude: [
|
||||
{ name: 'clear', description: 'Clear conversation history and free up context', source: 'builtin' },
|
||||
{ name: 'compact', description: 'Clear conversation history but keep a summary in context', source: 'builtin' },
|
||||
{ name: 'context', description: 'Visualize current context usage as a colored grid', source: 'builtin' },
|
||||
{ name: 'cost', description: 'Show the total cost and duration of the current session', source: 'builtin' },
|
||||
{ name: 'doctor', description: 'Diagnose and verify your Claude Code installation and settings', source: 'builtin' },
|
||||
{ name: 'plan', description: 'View or open the current session plan', source: 'builtin' },
|
||||
{ name: 'stats', description: 'Show your Claude Code usage statistics and activity', source: 'builtin' },
|
||||
{ name: 'status', description: 'Show Claude Code status including version, model, account, and API connectivity', source: 'builtin' },
|
||||
],
|
||||
codex: [
|
||||
{ name: 'clear', description: 'Clear current Codex thread context', source: 'builtin' },
|
||||
{ name: 'compact', description: 'Compact current Codex thread context', source: 'builtin' },
|
||||
{ name: 'goal', description: 'Set, view, pause, resume, or clear a persistent Codex goal', source: 'builtin' },
|
||||
{ name: 'help', description: 'Show supported HAPI Codex slash commands', source: 'builtin' },
|
||||
{ name: 'plan', description: 'Enable plan mode; use /plan off to return to default', source: 'builtin' },
|
||||
{ name: 'default', description: 'Return Codex collaboration mode to default', source: 'builtin' },
|
||||
{ name: 'execute', description: 'Return Codex collaboration mode to default', source: 'builtin' },
|
||||
{ name: 'status', description: 'Show current Codex session config', source: 'builtin' },
|
||||
{ name: 'model', description: 'Show or set Codex model, e.g. /model gpt-5.5', source: 'builtin' },
|
||||
{ name: 'reasoning', description: 'Show or set reasoning effort', source: 'builtin' },
|
||||
{ name: 'effort', description: 'Alias for /reasoning', source: 'builtin' },
|
||||
{ name: 'permissions', description: 'Show or set permission mode', source: 'builtin' },
|
||||
{ name: 'permission', description: 'Alias for /permissions', source: 'builtin' },
|
||||
],
|
||||
gemini: [
|
||||
{ name: 'about', description: 'Show version info', source: 'builtin' },
|
||||
{ name: 'clear', description: 'Clear the screen and conversation history', source: 'builtin' },
|
||||
{ name: 'compress', description: 'Compress the context by replacing it with a summary', source: 'builtin' },
|
||||
{ name: 'stats', description: 'Check session stats', source: 'builtin' },
|
||||
],
|
||||
opencode: [],
|
||||
}
|
||||
import {
|
||||
getBuiltinSlashCommands,
|
||||
mergeSlashCommands
|
||||
} from '@hapi/protocol/slashCommands'
|
||||
import type { SlashCommand } from '@hapi/protocol/apiTypes'
|
||||
|
||||
const UNSUPPORTED_CODEX_BUILTIN_COMMANDS = new Set([
|
||||
'review',
|
||||
@@ -43,21 +12,7 @@ const UNSUPPORTED_CODEX_BUILTIN_COMMANDS = new Set([
|
||||
'diff',
|
||||
])
|
||||
|
||||
export function getBuiltinSlashCommands(agentType: string): SlashCommand[] {
|
||||
return BUILTIN_COMMANDS[agentType] ?? BUILTIN_COMMANDS.claude ?? []
|
||||
}
|
||||
|
||||
export function mergeSlashCommands(commands: readonly SlashCommand[]): SlashCommand[] {
|
||||
const commandMap = new Map<string, SlashCommand>()
|
||||
for (const command of commands) {
|
||||
const key = command.name.toLowerCase()
|
||||
if (commandMap.has(key)) {
|
||||
commandMap.delete(key)
|
||||
}
|
||||
commandMap.set(key, command)
|
||||
}
|
||||
return Array.from(commandMap.values())
|
||||
}
|
||||
export { getBuiltinSlashCommands, mergeSlashCommands }
|
||||
|
||||
export function findCodexCustomPromptExpansion(
|
||||
text: string,
|
||||
|
||||
+2
-14
@@ -21,6 +21,8 @@ export type {
|
||||
OpencodeModelsResponse,
|
||||
OpencodeModelSummary,
|
||||
PathExistsResponse,
|
||||
SlashCommand,
|
||||
SlashCommandsResponse,
|
||||
UploadFileResponse
|
||||
} from '@hapi/protocol/apiTypes'
|
||||
|
||||
@@ -155,20 +157,6 @@ export type GitStatusFiles = {
|
||||
totalUnstaged: number
|
||||
}
|
||||
|
||||
export type SlashCommand = {
|
||||
name: string
|
||||
description?: string
|
||||
source: 'builtin' | 'user' | 'plugin' | 'project'
|
||||
content?: string // Expanded content for Codex user prompts
|
||||
pluginName?: string
|
||||
}
|
||||
|
||||
export type SlashCommandsResponse = {
|
||||
success: boolean
|
||||
commands?: SlashCommand[]
|
||||
error?: string
|
||||
}
|
||||
|
||||
export type SkillSummary = {
|
||||
name: string
|
||||
description?: string
|
||||
|
||||
Reference in New Issue
Block a user