dsh-slash-command 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +56 -2
- package/cordis.patch.yml +4 -0
- package/lib/client.js +505 -0
- package/lib/client.js.map +7 -0
- package/lib/core.js +127 -0
- package/lib/core.js.map +7 -0
- package/lib/index.js +309 -0
- package/lib/index.js.map +7 -0
- package/lib/types/client/index.d.ts +27 -0
- package/lib/types/client/locales.d.ts +44 -0
- package/lib/types/client/panel.d.ts +8 -0
- package/lib/types/core.d.ts +58 -0
- package/lib/types/index.d.ts +8 -0
- package/package.json +76 -4
package/lib/index.js.map
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": 3,
|
|
3
|
+
"sources": ["../src/index.ts", "../src/core.ts"],
|
|
4
|
+
"sourcesContent": ["/**\n * dsh-slash-command host \u534A\u8FB9\uFF1A\u8BFB `$DSH_HOME/slash-commands.json`\uFF0C\u4E3A\u6BCF\u4E2A name/alias \u6CE8\u518C\u659C\u6760\u547D\u4EE4\uFF0C\n * \u76D1\u542C\u6587\u4EF6\u70ED\u91CD\u8F7D\uFF0C\u5E76\u901A\u8FC7 `connection.fetch` \u66B4\u9732 UI \u7528\u7684 read/save \u63A5\u53E3\uFF08\u6CBF\u7528\u5BBF\u4E3B\u8BA4\u8BC1\u6805\u680F\uFF0C\u4EC5 owner \u53EF\u8FBE\uFF09\u3002\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport { createUserMessage } from '@deepseek-ai/dsh-llm'\nimport { execFile } from 'node:child_process'\nimport { watch } from 'node:fs'\nimport { mkdir, readFile, rename, writeFile } from 'node:fs/promises'\nimport { homedir } from 'node:os'\nimport { basename, dirname, join } from 'node:path'\nimport {\n composeAlias, composePrompt, execArgs, expandNames, formatExecResult, validateConfig,\n type ExecOutcome, type SlashCommand,\n} from './core.ts'\n\nexport const name = 'dsh-slash-command'\nexport const inject = ['commands', 'connection']\n\nconst READ_PATH = '/api/dsh-slash-command/read'\nconst SAVE_PATH = '/api/dsh-slash-command/save'\nconst MAX_BUFFER = 4 * 1024 * 1024\nconst MAX_BODY = 1024 * 1024\n/** alias \u94FE\u6700\u5927\u6DF1\u5EA6\uFF0C\u9632 a \u2192 b \u2192 a \u8FD9\u7C7B\u5FAA\u73AF\u3002 */\nconst MAX_ALIAS_DEPTH = 8\nconst ALIAS_DEPTH = Symbol('dsh-slash-command.aliasDepth')\n\n// \u5BBF\u4E3B\u547D\u4EE4 / agent / connection \u7684\u6700\u5C0F\u7ED3\u6784\u9762\uFF0C\u907F\u514D\u4F9D\u8D56\u989D\u5916\u7C7B\u578B\u5305\u3002\ntype Result = { kind: 'success'; text?: string; sourceEventSeq?: number } | { kind: 'error'; text: string }\ninterface AgentFace { steer(message: unknown): void; ctx?: unknown }\ninterface Invocation { agent: AgentFace; rawInput: string; signal: AbortSignal; [ALIAS_DEPTH]?: number }\ninterface CommandDef {\n name: string\n description: string\n input: { hint: string }\n handler: (inv: Invocation) => Result | Promise<Result>\n}\ninterface CommandsFace {\n register(def: CommandDef): () => void\n find(agent: AgentFace, name: string): CommandDef | undefined\n}\ninterface ConnectionFace {\n fetch: {\n register(route: {\n path: string\n methods: ('GET' | 'POST')[]\n requestBody: 'buffered'\n fetch: (request: Request) => Promise<Response>\n }): () => Promise<void>\n }\n}\ninterface WorkingDirectoryFace { ensure(agent: unknown, signal?: AbortSignal): Promise<string> }\n\nconst configPath = (): string => join(process.env.DSH_HOME ?? join(homedir(), '.dsh'), 'slash-commands.json')\n\nconst json = (status: number, body: unknown): Response =>\n new Response(JSON.stringify(body), { status, headers: { 'content-type': 'application/json', 'cache-control': 'no-store' } })\n\nfunction steerText(agent: AgentFace, text: string): void {\n agent.steer(createUserMessage({ content: [{ type: 'text', text }], source: { kind: 'user' } }))\n}\n\n/** \u4F1A\u8BDD\u5DE5\u4F5C\u533A\uFF1B\u53D6\u4E0D\u5230\u56DE\u843D process.cwd()\u3002 */\nasync function workspaceOf(ctx: Context, agent: AgentFace, signal: AbortSignal): Promise<string> {\n try {\n const wd = Reflect.get(ctx, 'workingDirectory') as WorkingDirectoryFace | undefined\n if (wd !== undefined) return await wd.ensure(agent, signal)\n } catch { /* \u56DE\u843D */ }\n return process.cwd()\n}\n\nfunction runExec(command: string, rawInput: string, cwd: string, timeout: number, signal: AbortSignal): Promise<ExecOutcome> {\n const { env, argv } = execArgs(rawInput)\n return new Promise((resolve) => {\n // bash -c script name args\u2026\uFF1Aargv \u6210\u4E3A $1\u2026\uFF0C\u811A\u672C\u6587\u672C\u91CC\u4E0D\u62FC\u4EFB\u4F55\u7528\u6237\u8F93\u5165\n execFile('/bin/bash', ['-lc', command, 'dsh-slash-command', ...argv], {\n cwd, timeout, maxBuffer: MAX_BUFFER, signal, env: { ...process.env, ...env },\n }, (err, stdout, stderr) => {\n if (err === null) return resolve({ exit: '0', stdout, stderr })\n const e = err as NodeJS.ErrnoException & { killed?: boolean; signal?: string }\n if (typeof e.code === 'number') return resolve({ exit: String(e.code), stdout, stderr })\n // \u8D85\u65F6/\u4FE1\u53F7/spawn \u5931\u8D25\uFF1A\u6CA1\u6709\u6570\u5B57\u9000\u51FA\u7801\n const exit = e.killed === true ? `timeout(${e.signal ?? 'killed'})` : String(e.code ?? e.signal ?? 'error')\n resolve({ exit, stdout, stderr: stderr + (stderr === '' || stderr.endsWith('\\n') ? '' : '\\n') + e.message })\n })\n })\n}\n\nfunction makeHandler(ctx: Context, commands: CommandsFace, cmd: SlashCommand): (inv: Invocation) => Promise<Result> {\n if (cmd.type === 'alias') {\n // \u76F4\u63A5\u8C03\u7528\u76EE\u6807\u547D\u4EE4\u7684 handler\uFF1A\u7ED3\u679C\u843D\u5728\u672C\u6761\u547D\u4EE4\u5361\u7247\u91CC\uFF0C\u4E0D\u518D\u591A\u51FA\u4E00\u5F20\u5361\n return async (inv) => {\n const depth = (inv[ALIAS_DEPTH] ?? 0) + 1\n if (depth > MAX_ALIAS_DEPTH) return { kind: 'error', text: `/${cmd.name}: alias chain too deep` }\n const line = composeAlias(cmd.target!, inv.rawInput)\n const m = /^\\/([a-z][a-z0-9_-]*)/.exec(line)\n const target = m === null ? undefined : commands.find(inv.agent, m[1]!)\n if (target === undefined) return { kind: 'error', text: `${line.split(/\\s/)[0]}: unknown command` }\n return target.handler({ ...inv, rawInput: line.slice(m![0].length), [ALIAS_DEPTH]: depth })\n }\n }\n if (cmd.type === 'prompt') {\n return (inv) => {\n steerText(inv.agent, composePrompt(cmd.prompt!, inv.rawInput))\n return Promise.resolve({ kind: 'success', text: '\u5DF2\u53D1\u9001' })\n }\n }\n return async (inv) => {\n const cwd = cmd.cwd === undefined || cmd.cwd === 'workspace' ? await workspaceOf(ctx, inv.agent, inv.signal) : cmd.cwd\n // \u5BF9\u6807 Hermes quick_commands\uFF1A\u53EA\u5728\u672C\u673A\u6267\u884C\u5E76\u56DE\u663E\uFF0C\u4E0D steer\u3001\u4E0D\u89E6\u53D1\u6A21\u578B\u56DE\u5408\u3002\n const outcome = await runExec(cmd.command!, inv.rawInput, cwd, cmd.timeoutMs!, inv.signal)\n const text = formatExecResult(outcome)\n return outcome.exit === '0' ? { kind: 'success', text } : { kind: 'error', text }\n }\n}\n\nexport function apply(ctx: Context): void {\n const commands = Reflect.get(ctx, 'commands') as CommandsFace\n const connection = Reflect.get(ctx, 'connection') as ConnectionFace\n const file = configPath()\n let disposers: (() => void)[] = []\n /** \u4E0A\u4E00\u4EFD\u5DF2\u751F\u6548\u7684\u539F\u59CB\u6587\u672C\uFF1B\u76F8\u540C\u5219\u4E0D\u91CD\u590D\u91CD\u6CE8\u518C\u3002 */\n let applied: string | undefined\n\n const unregisterAll = (): void => {\n for (const d of disposers) d()\n disposers = []\n }\n\n /** \u5168\u91CF\u53CD\u6CE8\u518C\u518D\u91CD\u6CE8\u518C\u3002\u89E3\u6790\u5931\u8D25\u65F6\u4FDD\u7559\u4E0A\u4E00\u4EFD\u3002 */\n async function reload(): Promise<void> {\n let text: string\n try {\n text = await readFile(file, 'utf8')\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code !== 'ENOENT') {\n ctx.logger.warn(`dsh-slash-command: cannot read ${file}: ${String(e)}`)\n return\n }\n text = '{\"commands\":[]}'\n }\n if (text === applied) return\n let raw: unknown\n try {\n raw = JSON.parse(text)\n } catch (e) {\n ctx.logger.warn(`dsh-slash-command: ${file} is not valid JSON, keeping previous config: ${String(e)}`)\n return\n }\n const { commands: valid, errors } = validateConfig(raw)\n for (const err of errors) {\n ctx.logger.warn(`dsh-slash-command: skipped commands[${err.index ?? '?'}]${err.field ? `.${err.field}` : ''}: ${err.message}`)\n }\n unregisterAll()\n applied = text\n for (const cmd of valid) {\n const handler = makeHandler(ctx, commands, cmd)\n for (const n of expandNames(cmd)) {\n try {\n disposers.push(commands.register({ name: n, description: cmd.description, input: { hint: '[args]' }, handler }))\n } catch (e) {\n ctx.logger.warn(`dsh-slash-command: cannot register /${n}: ${e instanceof Error ? e.message : String(e)}`)\n }\n }\n }\n }\n\n // watch \u76EE\u5F55\u800C\u975E\u6587\u4EF6\uFF1A\u7F16\u8F91\u5668/\u539F\u5B50 rename \u4F1A\u66FF\u6362 inode\u3002\n ctx.effect(() => {\n let timer: NodeJS.Timeout | undefined\n const trigger = (): void => {\n clearTimeout(timer)\n timer = setTimeout(() => { void reload() }, 300)\n }\n let watcher: ReturnType<typeof watch> | undefined\n void mkdir(dirname(file), { recursive: true }).then(() => {\n watcher = watch(dirname(file), (_event, filename) => { if (filename === basename(file)) trigger() })\n watcher.on('error', e => ctx.logger.warn(`dsh-slash-command: watcher error: ${String(e)}`))\n }).catch(e => ctx.logger.warn(`dsh-slash-command: cannot watch ${file}: ${String(e)}`))\n void reload()\n return () => {\n clearTimeout(timer)\n watcher?.close()\n unregisterAll()\n }\n }, 'dsh-slash-command: config watcher')\n\n connection.fetch.register({\n path: READ_PATH,\n methods: ['GET'],\n requestBody: 'buffered',\n fetch: async () => {\n try {\n const raw: unknown = JSON.parse(await readFile(file, 'utf8'))\n const commands = (raw as { commands?: unknown }).commands\n return json(200, { ok: true, commands: Array.isArray(commands) ? commands : [] })\n } catch (e) {\n if ((e as NodeJS.ErrnoException).code === 'ENOENT') return json(200, { ok: true, commands: [] })\n return json(500, { ok: false, errors: [{ code: 'type', message: `cannot read config: ${String(e)}` }] })\n }\n },\n })\n\n connection.fetch.register({\n path: SAVE_PATH,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: async (request) => {\n // \u4FDD\u5B58\u4F1A\u5199\u5165\u53EF\u6267\u884C\u547D\u4EE4\uFF1A\u62D2\u7EDD\u8DE8\u7AD9\u4E0E\u975E JSON \u8BF7\u6C42\uFF08\u975E\u7B80\u5355 content-type \u4F1A\u5F3A\u5236 CORS \u9884\u68C0\uFF0C\u5BBF\u4E3B\u4E0D\u4F1A\u653E\u884C\uFF09\u3002\n if (!(request.headers.get('content-type') ?? '').toLowerCase().startsWith('application/json')) {\n return json(415, { ok: false, errors: [{ code: 'type', message: 'content-type must be application/json' }] })\n }\n const origin = request.headers.get('origin')\n if (origin !== null && origin !== new URL(request.url).origin) {\n return json(403, { ok: false, errors: [{ code: 'type', message: 'cross-origin request rejected' }] })\n }\n const body = await request.text()\n if (body.length > MAX_BODY) return json(413, { ok: false, errors: [{ code: 'range', message: 'body too large' }] })\n let raw: unknown\n try { raw = JSON.parse(body) } catch { return json(400, { ok: false, errors: [{ code: 'type', message: 'body is not valid JSON' }] }) }\n const { commands: valid, errors } = validateConfig(raw)\n if (errors.length > 0) return json(400, { ok: false, errors })\n const text = `${JSON.stringify({ commands: valid }, null, 2)}\\n`\n await mkdir(dirname(file), { recursive: true })\n const tmp = `${file}.${process.pid}.tmp`\n await writeFile(tmp, text)\n await rename(tmp, file)\n await reload() // \u7ACB\u5373\u751F\u6548\uFF0C\u4E0D\u7B49 watcher \u7684 debounce\n return json(200, { ok: true, commands: valid })\n },\n })\n}\n", "/** \u914D\u7F6E\u89E3\u6790/\u6821\u9A8C\u4E0E\u6587\u672C\u62FC\u88C5\uFF1A\u7EAF\u51FD\u6570\uFF0C\u65E0 host \u4F9D\u8D56\uFF0C\u6D4B\u8BD5\u76F4\u63A5\u5F15\u7528\u3002 */\n\nexport const NAME_RE = /^[a-z][a-z0-9_-]*$/\nexport const DEFAULT_TIMEOUT_MS = 30_000\nexport const MAX_TIMEOUT_MS = 600_000\nexport const OUTPUT_LIMIT = 16 * 1024\n\nexport interface SlashCommand {\n name: string\n aliases: string[]\n description: string\n type: 'prompt' | 'exec' | 'alias'\n prompt?: string\n command?: string\n /** alias \u578B\uFF1A\u53E6\u4E00\u6761\u659C\u6760\u547D\u4EE4\uFF0C\u5982 `/compact` \u6216 `/review \u53EA\u770B src`\u3002 */\n target?: string\n cwd?: string\n timeoutMs?: number\n}\n\n/** `index` \u6307\u5411 commands \u6570\u7EC4\u4E0B\u6807\uFF1B\u7F3A\u7701\u8868\u793A\u9876\u5C42\u9519\u8BEF\u3002 */\nexport interface FieldError {\n index?: number\n field?: string\n code: 'type' | 'required' | 'pattern' | 'duplicate' | 'enum' | 'range'\n message: string\n}\n\n/** alias \u76EE\u6807\uFF1A`/name` \u540E\u53EF\u8DDF\u56FA\u5B9A\u53C2\u6570\uFF1B\u5F00\u5934\u7684 `/` \u53EF\u7701\u7565\u3002 */\nconst TARGET_RE = /^\\/?([a-z][a-z0-9_-]*)(?:\\s[\\s\\S]*)?$/\n\nconst isObj = (v: unknown): v is Record<string, unknown> => typeof v === 'object' && v !== null && !Array.isArray(v)\nconst nonEmpty = (v: unknown): v is string => typeof v === 'string' && v.trim() !== ''\n\n/**\n * \u6821\u9A8C\u5E76\u89C4\u8303\u5316\u914D\u7F6E\u3002\u975E\u6CD5\u6761\u76EE\u4E0D\u4F1A\u51FA\u73B0\u5728 `commands` \u91CC\uFF08\u6574\u6761\u4E22\u5F03\uFF09\uFF0C\u539F\u56E0\u5199\u5165 `errors`\u3002\n * \u540D\u5B57\u4E0E\u522B\u540D\u5171\u7528\u540C\u4E00\u547D\u540D\u7A7A\u95F4\uFF0C\u5148\u51FA\u73B0\u8005\u80DC\uFF0C\u540E\u8005\u62A5 duplicate\u3002\n */\nexport function validateConfig(raw: unknown): { commands: SlashCommand[]; errors: FieldError[] } {\n const commands: SlashCommand[] = []\n const errors: FieldError[] = []\n if (!isObj(raw) || !Array.isArray(raw.commands)) {\n return { commands, errors: [{ code: 'type', field: 'commands', message: 'commands must be an array' }] }\n }\n const seen = new Set<string>()\n raw.commands.forEach((item: unknown, index) => {\n const bad: FieldError[] = []\n const fail = (field: string, code: FieldError['code'], message: string): void => {\n bad.push({ index, field, code, message })\n }\n if (!isObj(item)) {\n errors.push({ index, code: 'type', message: 'command must be an object' })\n return\n }\n const names: string[] = []\n if (typeof item.name !== 'string' || item.name === '') fail('name', 'required', 'name is required')\n else if (!NAME_RE.test(item.name)) fail('name', 'pattern', `name \"${item.name}\" must match ${String(NAME_RE)}`)\n else names.push(item.name)\n const aliases: string[] = []\n if (item.aliases !== undefined) {\n if (!Array.isArray(item.aliases)) fail('aliases', 'type', 'aliases must be an array of strings')\n else {\n for (const a of item.aliases as unknown[]) {\n if (typeof a !== 'string' || !NAME_RE.test(a)) fail('aliases', 'pattern', `alias \"${String(a)}\" must match ${String(NAME_RE)}`)\n else aliases.push(a)\n }\n }\n }\n const own = [...names, ...aliases]\n own.forEach((n, i) => {\n if (seen.has(n) || own.indexOf(n) !== i) fail(n === item.name ? 'name' : 'aliases', 'duplicate', `\"${n}\" is already used`)\n })\n if (!nonEmpty(item.description)) fail('description', 'required', 'description is required')\n if (item.type !== 'prompt' && item.type !== 'exec' && item.type !== 'alias') fail('type', 'enum', 'type must be \"prompt\", \"exec\" or \"alias\"')\n else if (item.type === 'prompt' && !nonEmpty(item.prompt)) fail('prompt', 'required', 'prompt is required for prompt commands')\n else if (item.type === 'exec' && !nonEmpty(item.command)) fail('command', 'required', 'command is required for exec commands')\n else if (item.type === 'alias') {\n const m = typeof item.target === 'string' ? TARGET_RE.exec(item.target.trim()) : null\n if (!nonEmpty(item.target)) fail('target', 'required', 'target is required for alias commands')\n else if (m === null) fail('target', 'pattern', `target \"${String(item.target)}\" must be a slash command like /compact`)\n else if (own.includes(m[1]!)) fail('target', 'duplicate', `alias /${m[1]} cannot target itself`)\n }\n if (item.cwd !== undefined && !(typeof item.cwd === 'string' && (item.cwd === 'workspace' || item.cwd.startsWith('/')))) {\n fail('cwd', 'enum', 'cwd must be \"workspace\" or an absolute path')\n }\n if (item.timeoutMs !== undefined\n && !(typeof item.timeoutMs === 'number' && Number.isInteger(item.timeoutMs) && item.timeoutMs >= 1 && item.timeoutMs <= MAX_TIMEOUT_MS)) {\n fail('timeoutMs', 'range', `timeoutMs must be an integer in 1..${MAX_TIMEOUT_MS}`)\n }\n if (bad.length > 0) {\n errors.push(...bad)\n return\n }\n for (const n of own) seen.add(n)\n const cmd: SlashCommand = {\n name: item.name as string,\n aliases,\n description: (item.description as string).trim(),\n type: item.type as 'prompt' | 'exec',\n }\n if (cmd.type === 'prompt') cmd.prompt = item.prompt as string\n else if (cmd.type === 'alias') cmd.target = normalizeTarget(item.target as string)\n else {\n cmd.command = item.command as string\n cmd.cwd = (item.cwd as string | undefined) ?? 'workspace'\n cmd.timeoutMs = (item.timeoutMs as number | undefined) ?? DEFAULT_TIMEOUT_MS\n }\n commands.push(cmd)\n })\n return { commands, errors }\n}\n\n/** \u540D\u5B57 + \u522B\u540D\u5C55\u5F00\u6210\u8981\u6CE8\u518C\u7684\u5168\u90E8\u5165\u53E3\u540D\u3002 */\nexport const expandNames = (cmd: SlashCommand): string[] => [cmd.name, ...cmd.aliases]\n\n/** `$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u6CA1\u6709\u5360\u4F4D\u7B26\u4E14\u6709\u53C2\u6570\u65F6\u4EE5\u7A7A\u884C\u62FC\u5230\u672B\u5C3E\u3002 */\nexport function composePrompt(prompt: string, rawInput: string): string {\n const args = rawInput.trim()\n if (prompt.includes('$ARGUMENTS')) return prompt.replaceAll('$ARGUMENTS', () => args)\n return args === '' ? prompt : `${prompt}\\n\\n${args}`\n}\n\n/** \u7EDF\u4E00\u6210 `/name args` \u5F62\u5F0F\u3002 */\nexport const normalizeTarget = (target: string): string => {\n const t = target.trim()\n return t.startsWith('/') ? t : `/${t}`\n}\n\n/** alias \u5C55\u5F00\uFF1A`$ARGUMENTS` \u539F\u4F4D\u66FF\u6362\uFF1B\u5426\u5219\u53C2\u6570\u4EE5\u7A7A\u683C\u63A5\u5728\u76EE\u6807\u547D\u4EE4\u540E\u3002 */\nexport function composeAlias(target: string, rawInput: string): string {\n const args = rawInput.trim()\n if (target.includes('$ARGUMENTS')) return target.replaceAll('$ARGUMENTS', () => args).trim()\n return args === '' ? target : `${target} ${args}`\n}\n\n/**\n * exec \u53C2\u6570\u4EE5\u53D8\u91CF\u5F62\u5F0F\u4F20\u5165\u800C\u975E\u62FC\u8FDB\u811A\u672C\uFF08\u4E0D\u4F1A\u88AB\u5F53\u6210\u4EE3\u7801\u6267\u884C\uFF09\uFF1A\n * `$ARGUMENTS` = \u5B8C\u6574\u53C2\u6570\uFF0C`$1 $2 \u2026` / `\"$@\"` = \u6309\u7A7A\u767D\u62C6\u5206\u7684\u53C2\u6570\u3002\n */\nexport function execArgs(rawInput: string): { env: Record<string, string>; argv: string[] } {\n const args = rawInput.trim()\n return { env: { ARGUMENTS: args }, argv: args === '' ? [] : args.split(/\\s+/) }\n}\n\n/** \u6309 UTF-8 \u5B57\u8282\u622A\u65AD\uFF08\u4E0D\u62C6\u5B57\u7B26\uFF09\u3002 */\nfunction clip(text: string, limit: number): string {\n const buf = Buffer.from(text, 'utf8')\n if (buf.length <= limit) return text\n return `${buf.subarray(0, limit).toString('utf8').replace(/\uFFFD$/, '')}\\n\u2026[truncated]`\n}\n\nexport interface ExecOutcome {\n exit: string\n stdout: string\n stderr: string\n}\n\n/**\n * exec \u7ED3\u679C\u76F4\u63A5\u56DE\u663E\u5728\u547D\u4EE4\u5361\u7247\u91CC\uFF08\u4E0D\u8FDB\u6A21\u578B\uFF09\uFF1Astdout + stderr \u5408\u8BA1\u622A\u65AD\u5230 16KB\uFF1B\n * \u975E\u96F6\u9000\u51FA/\u8D85\u65F6\u5728\u672B\u5C3E\u9644 `exit=N`\uFF0C\u65E0\u8F93\u51FA\u65F6\u53EA\u7ED9 exit\u3002\n */\nexport function formatExecResult(o: ExecOutcome): string {\n const output = clip(`${o.stdout}${o.stderr}`.replace(/\\s+$/, ''), OUTPUT_LIMIT)\n if (o.exit === '0') return output === '' ? 'exit=0' : output\n return output === '' ? `exit=${o.exit}` : `${output}\\nexit=${o.exit}`\n}\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["commands"]
|
|
7
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** dsh-slash-command client 半边:设置弹窗里的「自定义命令」分区(settings.section)。 */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
import { type LocaleKey } from './locales.ts';
|
|
4
|
+
export declare const name = "dsh-slash-command";
|
|
5
|
+
export declare const inject: string[];
|
|
6
|
+
type Translate = (key: LocaleKey, params?: Record<string, unknown>) => string;
|
|
7
|
+
interface ClientContext extends Context {
|
|
8
|
+
slots: {
|
|
9
|
+
inject(key: string, callback: () => unknown): () => void;
|
|
10
|
+
register(options: {
|
|
11
|
+
name: string;
|
|
12
|
+
id: string;
|
|
13
|
+
order: number;
|
|
14
|
+
locale: string;
|
|
15
|
+
label: () => string;
|
|
16
|
+
}, component: unknown): () => void;
|
|
17
|
+
};
|
|
18
|
+
locale: {
|
|
19
|
+
register(ns: string, dictionaries: {
|
|
20
|
+
zh: Record<string, string>;
|
|
21
|
+
en: Record<string, string>;
|
|
22
|
+
}): () => void;
|
|
23
|
+
bind(ns: string): Translate;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function apply(ctx: ClientContext): void;
|
|
27
|
+
export {};
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/** 设置页「自定义命令」文案字典(zh/en);`/` 菜单文案来自配置文件本身,这里不管。 */
|
|
2
|
+
export declare const NS = "dsh-slash-command";
|
|
3
|
+
export declare const zh: {
|
|
4
|
+
readonly title: "自定义命令";
|
|
5
|
+
readonly add: "新增命令";
|
|
6
|
+
readonly edit: "编辑";
|
|
7
|
+
readonly editOf: "编辑 /{name}";
|
|
8
|
+
readonly delete: "删除";
|
|
9
|
+
readonly deleteOf: "删除 /{name}";
|
|
10
|
+
readonly deleteConfirm: "删除 /{name}?立即生效,无法撤销。";
|
|
11
|
+
readonly cancel: "取消";
|
|
12
|
+
readonly save: "保存";
|
|
13
|
+
readonly saving: "保存中…";
|
|
14
|
+
readonly saved: "已保存";
|
|
15
|
+
readonly retry: "重试";
|
|
16
|
+
readonly loadFailed: "读取失败:{detail}";
|
|
17
|
+
readonly saveFailed: "保存失败:{detail}";
|
|
18
|
+
readonly help: "{field}说明";
|
|
19
|
+
readonly 'field.type': "类型";
|
|
20
|
+
readonly 'field.name': "名称";
|
|
21
|
+
readonly 'field.aliases': "别名";
|
|
22
|
+
readonly 'field.description': "描述";
|
|
23
|
+
readonly 'field.prompt': "Prompt";
|
|
24
|
+
readonly 'field.command': "命令";
|
|
25
|
+
readonly 'field.target': "目标命令";
|
|
26
|
+
readonly 'field.cwd': "工作目录";
|
|
27
|
+
readonly 'field.timeoutMs': "超时(毫秒)";
|
|
28
|
+
readonly 'type.prompt': "Prompt";
|
|
29
|
+
readonly 'type.exec': "Shell";
|
|
30
|
+
readonly 'type.alias': "转发";
|
|
31
|
+
readonly 'tip.prompt': "$ARGUMENTS 替换为命令后的参数;未使用时参数追加到末尾。";
|
|
32
|
+
readonly 'tip.command': "$ARGUMENTS 为完整参数,$1 $2… 为按空格拆分的参数;参数只作变量传入,不会被当作命令执行。";
|
|
33
|
+
readonly 'tip.target': "转发到另一条斜杠命令,可带固定参数。$ARGUMENTS 替换为参数,未使用时参数接在末尾。";
|
|
34
|
+
readonly 'tip.cwd': "workspace 为当前会话目录,也可填绝对路径。";
|
|
35
|
+
readonly execWarn: "以 dsh 进程权限直接运行,不经审批。";
|
|
36
|
+
readonly 'err.type': "类型不对";
|
|
37
|
+
readonly 'err.required': "必填";
|
|
38
|
+
readonly 'err.pattern': "小写字母开头,仅含 a-z 0-9 - _";
|
|
39
|
+
readonly 'err.duplicate': "已被占用或指向自身";
|
|
40
|
+
readonly 'err.enum': "填 workspace 或绝对路径";
|
|
41
|
+
readonly 'err.range': "1–600000 之间的整数";
|
|
42
|
+
};
|
|
43
|
+
export type LocaleKey = keyof typeof zh;
|
|
44
|
+
export declare const en: Record<LocaleKey, string>;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** 设置页「自定义命令」分区:命令卡片列表 + 单卡行内编辑;每次保存/删除都整表写回服务端。 */
|
|
2
|
+
import { type ReactElement } from 'react';
|
|
3
|
+
import type { LocaleKey } from './locales.ts';
|
|
4
|
+
type T = (key: LocaleKey, params?: Record<string, unknown>) => string;
|
|
5
|
+
export declare function SlashCommandSection({ t }: {
|
|
6
|
+
t: T;
|
|
7
|
+
}): ReactElement;
|
|
8
|
+
export {};
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** 配置解析/校验与文本拼装:纯函数,无 host 依赖,测试直接引用。 */
|
|
2
|
+
export declare const NAME_RE: RegExp;
|
|
3
|
+
export declare const DEFAULT_TIMEOUT_MS = 30000;
|
|
4
|
+
export declare const MAX_TIMEOUT_MS = 600000;
|
|
5
|
+
export declare const OUTPUT_LIMIT: number;
|
|
6
|
+
export interface SlashCommand {
|
|
7
|
+
name: string;
|
|
8
|
+
aliases: string[];
|
|
9
|
+
description: string;
|
|
10
|
+
type: 'prompt' | 'exec' | 'alias';
|
|
11
|
+
prompt?: string;
|
|
12
|
+
command?: string;
|
|
13
|
+
/** alias 型:另一条斜杠命令,如 `/compact` 或 `/review 只看 src`。 */
|
|
14
|
+
target?: string;
|
|
15
|
+
cwd?: string;
|
|
16
|
+
timeoutMs?: number;
|
|
17
|
+
}
|
|
18
|
+
/** `index` 指向 commands 数组下标;缺省表示顶层错误。 */
|
|
19
|
+
export interface FieldError {
|
|
20
|
+
index?: number;
|
|
21
|
+
field?: string;
|
|
22
|
+
code: 'type' | 'required' | 'pattern' | 'duplicate' | 'enum' | 'range';
|
|
23
|
+
message: string;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* 校验并规范化配置。非法条目不会出现在 `commands` 里(整条丢弃),原因写入 `errors`。
|
|
27
|
+
* 名字与别名共用同一命名空间,先出现者胜,后者报 duplicate。
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateConfig(raw: unknown): {
|
|
30
|
+
commands: SlashCommand[];
|
|
31
|
+
errors: FieldError[];
|
|
32
|
+
};
|
|
33
|
+
/** 名字 + 别名展开成要注册的全部入口名。 */
|
|
34
|
+
export declare const expandNames: (cmd: SlashCommand) => string[];
|
|
35
|
+
/** `$ARGUMENTS` 原位替换;没有占位符且有参数时以空行拼到末尾。 */
|
|
36
|
+
export declare function composePrompt(prompt: string, rawInput: string): string;
|
|
37
|
+
/** 统一成 `/name args` 形式。 */
|
|
38
|
+
export declare const normalizeTarget: (target: string) => string;
|
|
39
|
+
/** alias 展开:`$ARGUMENTS` 原位替换;否则参数以空格接在目标命令后。 */
|
|
40
|
+
export declare function composeAlias(target: string, rawInput: string): string;
|
|
41
|
+
/**
|
|
42
|
+
* exec 参数以变量形式传入而非拼进脚本(不会被当成代码执行):
|
|
43
|
+
* `$ARGUMENTS` = 完整参数,`$1 $2 …` / `"$@"` = 按空白拆分的参数。
|
|
44
|
+
*/
|
|
45
|
+
export declare function execArgs(rawInput: string): {
|
|
46
|
+
env: Record<string, string>;
|
|
47
|
+
argv: string[];
|
|
48
|
+
};
|
|
49
|
+
export interface ExecOutcome {
|
|
50
|
+
exit: string;
|
|
51
|
+
stdout: string;
|
|
52
|
+
stderr: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* exec 结果直接回显在命令卡片里(不进模型):stdout + stderr 合计截断到 16KB;
|
|
56
|
+
* 非零退出/超时在末尾附 `exit=N`,无输出时只给 exit。
|
|
57
|
+
*/
|
|
58
|
+
export declare function formatExecResult(o: ExecOutcome): string;
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dsh-slash-command host 半边:读 `$DSH_HOME/slash-commands.json`,为每个 name/alias 注册斜杠命令,
|
|
3
|
+
* 监听文件热重载,并通过 `connection.fetch` 暴露 UI 用的 read/save 接口(沿用宿主认证栅栏,仅 owner 可达)。
|
|
4
|
+
*/
|
|
5
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
6
|
+
export declare const name = "dsh-slash-command";
|
|
7
|
+
export declare const inject: string[];
|
|
8
|
+
export declare function apply(ctx: Context): void;
|
package/package.json
CHANGED
|
@@ -1,6 +1,78 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "dsh-slash-command",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "DSH 自定义斜杠命令:别名 + prompt 型 + exec 型 + Web UI 配置",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"dsh",
|
|
7
|
+
"deepseek",
|
|
8
|
+
"deepseek-harness",
|
|
9
|
+
"slash-command",
|
|
10
|
+
"plugin"
|
|
11
|
+
],
|
|
12
|
+
"homepage": "https://github.com/Cassius0924/dsh-slash-command#readme",
|
|
13
|
+
"repository": {
|
|
14
|
+
"type": "git",
|
|
15
|
+
"url": "git+https://github.com/Cassius0924/dsh-slash-command.git"
|
|
16
|
+
},
|
|
17
|
+
"bugs": {
|
|
18
|
+
"url": "https://github.com/Cassius0924/dsh-slash-command/issues"
|
|
19
|
+
},
|
|
20
|
+
"author": "Cassius0924",
|
|
21
|
+
"type": "module",
|
|
22
|
+
"main": "lib/index.js",
|
|
23
|
+
"types": "lib/types/index.d.ts",
|
|
24
|
+
"exports": {
|
|
25
|
+
".": {
|
|
26
|
+
"types": "./lib/types/index.d.ts",
|
|
27
|
+
"default": "./lib/index.js"
|
|
28
|
+
},
|
|
29
|
+
"./client": {
|
|
30
|
+
"types": "./lib/types/client/index.d.ts",
|
|
31
|
+
"default": "./lib/client.js"
|
|
32
|
+
},
|
|
33
|
+
"./package.json": "./package.json"
|
|
34
|
+
},
|
|
35
|
+
"dsh": {
|
|
36
|
+
"bundle": {
|
|
37
|
+
"patch": "./cordis.patch.yml"
|
|
38
|
+
},
|
|
39
|
+
"client": {
|
|
40
|
+
"platform": "web",
|
|
41
|
+
"immediately": true,
|
|
42
|
+
"inject": [
|
|
43
|
+
"@deepseek-ai/dsh-client-runtime",
|
|
44
|
+
"@deepseek-ai/dsh-client-locale"
|
|
45
|
+
]
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"files": [
|
|
49
|
+
"lib",
|
|
50
|
+
"cordis.patch.yml",
|
|
51
|
+
"README.md",
|
|
52
|
+
"LICENSE"
|
|
53
|
+
],
|
|
54
|
+
"scripts": {
|
|
55
|
+
"build": "node build.mjs",
|
|
56
|
+
"test": "node --test test/*.mjs",
|
|
57
|
+
"prepare": "node build.mjs"
|
|
58
|
+
},
|
|
59
|
+
"license": "MIT",
|
|
60
|
+
"engines": {
|
|
61
|
+
"node": ">=20"
|
|
62
|
+
},
|
|
63
|
+
"peerDependencies": {
|
|
64
|
+
"@deepseek-ai/cordis": "^4.0.0",
|
|
65
|
+
"react": "^18.2.0"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"@deepseek-ai/cordis": "^4.0.1",
|
|
69
|
+
"@types/node": "^24.0.0",
|
|
70
|
+
"@types/react": "^18.2.0",
|
|
71
|
+
"esbuild": "^0.25.0",
|
|
72
|
+
"react": "^18.2.0",
|
|
73
|
+
"typescript": "^5.6.0"
|
|
74
|
+
},
|
|
75
|
+
"publishConfig": {
|
|
76
|
+
"access": "public"
|
|
77
|
+
}
|
|
78
|
+
}
|