dsh-fishpai 1.0.2
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 +22 -0
- package/NOTICE.md +46 -0
- package/README.md +169 -0
- package/cordis.patch.yml +10 -0
- package/lib/client.js +2770 -0
- package/package.json +65 -0
- package/plugin/core/diff.mjs +208 -0
- package/plugin/core/markdown.mjs +450 -0
- package/plugin/core/notes.mjs +151 -0
- package/plugin/core/patch.mjs +93 -0
- package/plugin/core/render.mjs +906 -0
- package/plugin/core/runtime.mjs +83 -0
- package/plugin/core/theme-info.mjs +80 -0
- package/plugin/core/theme-spec.mjs +276 -0
- package/plugin/host/assets.mjs +120 -0
- package/plugin/host/custom-theme.mjs +121 -0
- package/plugin/host/routes.mjs +494 -0
- package/plugin/host/store.mjs +719 -0
- package/plugin/host/tools.mjs +573 -0
- package/plugin/index.mjs +127 -0
- package/plugin/vendor/highlight.min.js +1213 -0
- package/plugin/vendor/hljs-map.json +202 -0
- package/plugin/vendor/markdown-it.min.js +2 -0
- package/plugin/vendor/themes.js +414 -0
- package/skills/fishpai/SKILL.md +146 -0
package/plugin/index.mjs
ADDED
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 鱼排 FishPai —— 宿主入口。
|
|
3
|
+
*
|
|
4
|
+
* 三件事,都挂在 ctx.effect 上(HMR / 卸载时逐条收回):
|
|
5
|
+
* 1. 注册模型工具:fishpai_open / fishpai_read / fishpai_write / fishpai_render / fishpai_theme
|
|
6
|
+
* 2. 注册 `/fishpai/api/*` 路由:右侧栏面板读写文档的唯一通道
|
|
7
|
+
* 3. 注册 `fishpai` 技能:告诉模型什么时候用、按什么纪律改稿
|
|
8
|
+
*
|
|
9
|
+
* 客户端半边在 `lib/client.js`(由 client/*.tsx 打包),通过 package.json 的
|
|
10
|
+
* `dsh.client` + `exports["./client"]` 声明,与本文件同属一个包。
|
|
11
|
+
*/
|
|
12
|
+
import { existsSync, readFileSync } from 'node:fs'
|
|
13
|
+
import path from 'node:path'
|
|
14
|
+
import { fileURLToPath } from 'node:url'
|
|
15
|
+
import { registerTools } from './host/tools.mjs'
|
|
16
|
+
import { createApiHandler, API_PREFIX } from './host/routes.mjs'
|
|
17
|
+
import { canonicalCwd, ensureFishpaiLayout } from './host/store.mjs'
|
|
18
|
+
|
|
19
|
+
export const name = 'dsh-fishpai'
|
|
20
|
+
|
|
21
|
+
/** 少一个都会被 Cordis 拒绝访问:工具、路由、会话目录、技能。 */
|
|
22
|
+
export const inject = ['webServer', 'sessions', 'tools', 'skills']
|
|
23
|
+
|
|
24
|
+
const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..')
|
|
25
|
+
const SKILL_DIR = path.join(ROOT, 'skills', 'fishpai')
|
|
26
|
+
const SKILL_FILE = path.join(SKILL_DIR, 'SKILL.md')
|
|
27
|
+
|
|
28
|
+
/** 会话 → 工作目录(规范化成真实路径,与路径守卫同一口径)。 */
|
|
29
|
+
function makeResolveCwd(ctx) {
|
|
30
|
+
return (sessionId) => {
|
|
31
|
+
try {
|
|
32
|
+
const session = ctx.sessions && ctx.sessions.get ? ctx.sessions.get(sessionId) : null
|
|
33
|
+
const cwd = session && session.header ? session.header.cwd : null
|
|
34
|
+
if (cwd) return canonicalCwd(cwd)
|
|
35
|
+
} catch {
|
|
36
|
+
/* 会话已消失就往下走 */
|
|
37
|
+
}
|
|
38
|
+
return null
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** 极简 frontmatter 读取(技能只需要 name / description)。 */
|
|
43
|
+
function readSkillFrontmatter(source) {
|
|
44
|
+
const m = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/.exec(source)
|
|
45
|
+
if (!m) return {}
|
|
46
|
+
const out = {}
|
|
47
|
+
for (const line of m[1].split(/\r?\n/)) {
|
|
48
|
+
const kv = /^([A-Za-z0-9_-]+):\s*(.*)$/.exec(line)
|
|
49
|
+
if (kv) out[kv[1]] = kv[2].trim()
|
|
50
|
+
}
|
|
51
|
+
return out
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function registerSkill(ctx, log) {
|
|
55
|
+
if (!existsSync(SKILL_FILE)) return () => {}
|
|
56
|
+
try {
|
|
57
|
+
const source = readFileSync(SKILL_FILE, 'utf8')
|
|
58
|
+
const frontmatter = readSkillFrontmatter(source)
|
|
59
|
+
const body = source.replace(/^---\r?\n[\s\S]*?\r?\n---\r?\n?/, '')
|
|
60
|
+
const off = ctx.skills.register({
|
|
61
|
+
name: frontmatter.name || 'fishpai',
|
|
62
|
+
description: frontmatter.description || '公众号排版(鱼排)',
|
|
63
|
+
content: body.trim(),
|
|
64
|
+
// ⚠️ `source` **必须给**:runtime 技能的 register() 只校验 name/description,
|
|
65
|
+
// 但技能被 load 时会走 validateDefinition,那里要求 source / provider 都是字符串
|
|
66
|
+
// (provider 由注册器补默认值,source 不会)。漏了它,技能在目录里看得见、一加载就报
|
|
67
|
+
// "loaded skill ... source must be a string"。
|
|
68
|
+
source: 'bundled',
|
|
69
|
+
resourceBase: { kind: 'directory', path: SKILL_DIR },
|
|
70
|
+
})
|
|
71
|
+
return typeof off === 'function' ? off : () => {}
|
|
72
|
+
} catch (error) {
|
|
73
|
+
log(`[fishpai] 技能注册失败:${error.message}`)
|
|
74
|
+
return () => {}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function apply(ctx) {
|
|
79
|
+
const log = (msg) => {
|
|
80
|
+
try {
|
|
81
|
+
ctx.logger?.info?.(msg)
|
|
82
|
+
} catch {
|
|
83
|
+
/* 没有 logger 也不影响功能 */
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
const resolveCwd = makeResolveCwd(ctx)
|
|
87
|
+
|
|
88
|
+
// 工作目录里的 .fishpai 布局在第一次真正用到时创建;这里只做一次尽力而为的预处理
|
|
89
|
+
ctx.effect(() => {
|
|
90
|
+
try {
|
|
91
|
+
const sessionList = ctx.sessions && typeof ctx.sessions.list === 'function' ? ctx.sessions.list() : []
|
|
92
|
+
for (const session of sessionList) {
|
|
93
|
+
const cwd = session && session.header ? session.header.cwd : null
|
|
94
|
+
if (cwd) ensureFishpaiLayout(cwd)
|
|
95
|
+
}
|
|
96
|
+
} catch {
|
|
97
|
+
/* 拿不到会话列表就等第一次工具调用时再建 */
|
|
98
|
+
}
|
|
99
|
+
return () => {}
|
|
100
|
+
}, 'fishpai: 初始化 .fishpai 布局')
|
|
101
|
+
|
|
102
|
+
// ── 1. 模型工具 ────────────────────────────────────────────
|
|
103
|
+
ctx.effect(() => registerTools(ctx, { resolveCwd, log }), 'fishpai: 注册模型工具')
|
|
104
|
+
|
|
105
|
+
// ── 2. HTTP 路由 ───────────────────────────────────────────
|
|
106
|
+
ctx.effect(() => {
|
|
107
|
+
const webServer = ctx.webServer
|
|
108
|
+
if (!webServer || typeof webServer.register !== 'function') {
|
|
109
|
+
log('[fishpai] webServer 不可用,右侧栏面板将无法读写文档')
|
|
110
|
+
return () => {}
|
|
111
|
+
}
|
|
112
|
+
try {
|
|
113
|
+
const handler = createApiHandler({ resolveCwd, log })
|
|
114
|
+
return webServer.register({ kind: 'prefix', path: API_PREFIX, handler })
|
|
115
|
+
} catch (error) {
|
|
116
|
+
// 路由注册失败不应把整个插件拖垮:工具(模型侧)仍然可用
|
|
117
|
+
log(`[fishpai] 路由注册失败:${error.message}`)
|
|
118
|
+
return () => {}
|
|
119
|
+
}
|
|
120
|
+
}, 'fishpai: /fishpai/api 路由')
|
|
121
|
+
|
|
122
|
+
// ── 3. 技能 ────────────────────────────────────────────────
|
|
123
|
+
ctx.effect(() => {
|
|
124
|
+
const off = registerSkill(ctx, log)
|
|
125
|
+
return () => off()
|
|
126
|
+
}, 'fishpai: 注册技能')
|
|
127
|
+
}
|