prompt-skill-armory 0.4.1
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 +112 -0
- package/cli.cjs +110 -0
- package/package.json +27 -0
- package/packages/client-ui-switchblade/src/client/SwitchbladeSection.tsx +614 -0
- package/packages/client-ui-switchblade/src/client/index.ts +75 -0
- package/packages/client-ui-switchblade/src/client/locales.ts +106 -0
- package/packages/client-ui-switchblade/src/client/store.ts +350 -0
- package/packages/client-ui-switchblade/src/css-modules.d.ts +5 -0
- package/packages/client-ui-switchblade/src/index.ts +12 -0
- package/packages/client-ui-switchblade/src/invariant.ts +10 -0
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switchblade management page dictionaries.
|
|
3
|
+
* @module @deepseek-ai/dsh-client-ui-switchblade
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** Locale namespace owned by this plugin (settings.* prefix per the slot contract). */
|
|
7
|
+
export const NS = 'settings.switchblade'
|
|
8
|
+
|
|
9
|
+
/** Keys translated by this plugin. */
|
|
10
|
+
export type SwitchbladeKey =
|
|
11
|
+
| 'nav' | 'skillsTitle' | 'presetsTitle' | 'promptsTitle' | 'commandsTitle'
|
|
12
|
+
| 'empty' | 'enabled' | 'disabled' | 'installed'
|
|
13
|
+
| 'refresh' | 'setDefault' | 'loadFailed'
|
|
14
|
+
| 'globalHint' | 'promptNamePlaceholder' | 'promptDescPlaceholder' | 'promptContentPlaceholder'
|
|
15
|
+
| 'addPrompt' | 'enable' | 'disable' | 'delete'
|
|
16
|
+
| 'installSkill' | 'skillNamePlaceholder' | 'skillDescPlaceholder' | 'skillContentPlaceholder'
|
|
17
|
+
| 'install' | 'uninstall' | 'pickSkillFile' | 'searchPlaceholder'
|
|
18
|
+
| 'installedSkills' | 'installedSkillsGoRight' | 'manage'
|
|
19
|
+
| 'localSkills' | 'agentPresetsTitle' | 'edit' | 'save' | 'cancel' | 'addSkill'
|
|
20
|
+
| 'pickZipFile' | 'cliHint'
|
|
21
|
+
|
|
22
|
+
/** zh-CN copy. */
|
|
23
|
+
export const zh: Record<SwitchbladeKey, string> = {
|
|
24
|
+
nav: 'Prompt-SkillArmory',
|
|
25
|
+
skillsTitle: '技能',
|
|
26
|
+
presetsTitle: '提示词预设',
|
|
27
|
+
promptsTitle: '提示词',
|
|
28
|
+
commandsTitle: '命令',
|
|
29
|
+
empty: '(空)',
|
|
30
|
+
enabled: '启用',
|
|
31
|
+
disabled: '停用',
|
|
32
|
+
installed: '已装',
|
|
33
|
+
refresh: '刷新',
|
|
34
|
+
setDefault: '设为默认',
|
|
35
|
+
loadFailed: '加载失败',
|
|
36
|
+
globalHint: '· 全局生效',
|
|
37
|
+
promptNamePlaceholder: '提示词名称',
|
|
38
|
+
promptDescPlaceholder: '描述(可选)',
|
|
39
|
+
promptContentPlaceholder: '提示词内容…',
|
|
40
|
+
addPrompt: '添加提示词',
|
|
41
|
+
enable: '启用',
|
|
42
|
+
disable: '停用',
|
|
43
|
+
delete: '删除',
|
|
44
|
+
installSkill: '技能',
|
|
45
|
+
skillNamePlaceholder: '技能名称(kebab-case)',
|
|
46
|
+
skillDescPlaceholder: '描述(可选)',
|
|
47
|
+
skillContentPlaceholder: '技能指令内容…',
|
|
48
|
+
install: '安装',
|
|
49
|
+
uninstall: '卸载',
|
|
50
|
+
pickSkillFile: '选择本地 .md 技能文件导入',
|
|
51
|
+
searchPlaceholder: '搜索…',
|
|
52
|
+
installedSkills: '本地化技能',
|
|
53
|
+
installedSkillsGoRight: '已安装的技能在右侧第四列管理',
|
|
54
|
+
manage: '托管',
|
|
55
|
+
localSkills: '本地扫描技能',
|
|
56
|
+
agentPresetsTitle: 'Agent预设',
|
|
57
|
+
edit: '编辑',
|
|
58
|
+
save: '保存',
|
|
59
|
+
cancel: '取消',
|
|
60
|
+
addSkill: '添加技能',
|
|
61
|
+
pickZipFile: '导入 .zip 技能包',
|
|
62
|
+
cliHint: 'CLI 直接安装(在会话里输入):',
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** en-US copy. */
|
|
66
|
+
export const en: Record<SwitchbladeKey, string> = {
|
|
67
|
+
nav: 'Prompt-SkillArmory',
|
|
68
|
+
skillsTitle: 'Skills',
|
|
69
|
+
presetsTitle: 'Prompt Presets',
|
|
70
|
+
promptsTitle: 'Prompts',
|
|
71
|
+
commandsTitle: 'Commands',
|
|
72
|
+
empty: '(empty)',
|
|
73
|
+
enabled: 'enabled',
|
|
74
|
+
disabled: 'disabled',
|
|
75
|
+
installed: 'installed',
|
|
76
|
+
refresh: 'Refresh',
|
|
77
|
+
setDefault: 'Set default',
|
|
78
|
+
loadFailed: 'Failed to load',
|
|
79
|
+
globalHint: '· global',
|
|
80
|
+
promptNamePlaceholder: 'Prompt name',
|
|
81
|
+
promptDescPlaceholder: 'Description (optional)',
|
|
82
|
+
promptContentPlaceholder: 'Prompt content…',
|
|
83
|
+
addPrompt: 'Add prompt',
|
|
84
|
+
enable: 'Enable',
|
|
85
|
+
disable: 'Disable',
|
|
86
|
+
delete: 'Delete',
|
|
87
|
+
installSkill: 'Skills',
|
|
88
|
+
skillNamePlaceholder: 'Skill name (kebab-case)',
|
|
89
|
+
skillDescPlaceholder: 'Description (optional)',
|
|
90
|
+
skillContentPlaceholder: 'Skill instructions…',
|
|
91
|
+
install: 'Install',
|
|
92
|
+
uninstall: 'Uninstall',
|
|
93
|
+
pickSkillFile: 'Pick a local .md skill file',
|
|
94
|
+
searchPlaceholder: 'Search…',
|
|
95
|
+
installedSkills: 'Local skills',
|
|
96
|
+
installedSkillsGoRight: 'Installed skills are managed in the right column',
|
|
97
|
+
manage: 'Manage',
|
|
98
|
+
localSkills: 'Scanned skills',
|
|
99
|
+
agentPresetsTitle: 'Agent Presets',
|
|
100
|
+
edit: 'Edit',
|
|
101
|
+
save: 'Save',
|
|
102
|
+
cancel: 'Cancel',
|
|
103
|
+
addSkill: 'Add skill',
|
|
104
|
+
pickZipFile: 'Import .zip skill bundle',
|
|
105
|
+
cliHint: 'Install via CLI (type in a session):',
|
|
106
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switchblade section data controller: reads the skill catalog and the prompt
|
|
3
|
+
* preset roster through the existing connection RPC surface (no Typert
|
|
4
|
+
* generation needed — these methods are already wired).
|
|
5
|
+
* @module @deepseek-ai/dsh-client-ui-switchblade
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { ConnectionHandle, SessionId } from '@deepseek-ai/dsh-api-remotes/client'
|
|
9
|
+
import { createSnapshotStore, type SnapshotStore } from '@deepseek-ai/dsh-client-runtime/client'
|
|
10
|
+
|
|
11
|
+
/** A skill row as reported by skill.list. */
|
|
12
|
+
export interface SkillRow {
|
|
13
|
+
readonly name: string
|
|
14
|
+
readonly description: string
|
|
15
|
+
readonly modelInvocable: boolean
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** A prompt-preset row as reported by agentPreset.list. */
|
|
19
|
+
export interface PresetRow {
|
|
20
|
+
readonly id: string
|
|
21
|
+
readonly name?: string
|
|
22
|
+
readonly description?: string
|
|
23
|
+
readonly isDefault: boolean
|
|
24
|
+
readonly trust: string
|
|
25
|
+
readonly broken?: string
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** A command row (not yet wired to an RPC; kept for the section's third group). */
|
|
29
|
+
export interface CommandRow {
|
|
30
|
+
readonly name: string
|
|
31
|
+
readonly description: string
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** One user-authored prompt (mirrors the Host ManagedPrompt). */
|
|
35
|
+
export interface PromptRow {
|
|
36
|
+
readonly id: string
|
|
37
|
+
readonly name: string
|
|
38
|
+
readonly description: string
|
|
39
|
+
readonly content: string
|
|
40
|
+
readonly order: number
|
|
41
|
+
readonly enabled: boolean
|
|
42
|
+
readonly isDefault: boolean
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/** An installed skill row. */
|
|
46
|
+
export interface InstalledSkillRow {
|
|
47
|
+
readonly name: string
|
|
48
|
+
readonly description: string
|
|
49
|
+
readonly content: string
|
|
50
|
+
/** Whether this skill is currently registered (enabled) as a runtime skill. */
|
|
51
|
+
readonly enabled: boolean
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** The section's loaded view state. */
|
|
55
|
+
export interface SwitchbladeSectionState {
|
|
56
|
+
readonly status: 'idle' | 'loading' | 'ready' | 'error'
|
|
57
|
+
readonly message?: string
|
|
58
|
+
readonly skills: readonly SkillRow[]
|
|
59
|
+
readonly presets: readonly PresetRow[]
|
|
60
|
+
readonly commands: readonly CommandRow[]
|
|
61
|
+
readonly prompts: readonly PromptRow[]
|
|
62
|
+
readonly installedSkills: readonly InstalledSkillRow[]
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Initial (idle) state. */
|
|
66
|
+
const IDLE: SwitchbladeSectionState = {
|
|
67
|
+
status: 'idle',
|
|
68
|
+
skills: [],
|
|
69
|
+
presets: [],
|
|
70
|
+
commands: [],
|
|
71
|
+
prompts: [],
|
|
72
|
+
installedSkills: [],
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Normalize a thrown wire error to a message. */
|
|
76
|
+
function messageOf(error: unknown): string {
|
|
77
|
+
return error instanceof Error ? error.message : String(error)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Data controller bound to one session's connection.
|
|
82
|
+
* @param api - the connection's API client.
|
|
83
|
+
* @param sessionId - session the skill catalog resolves against.
|
|
84
|
+
*/
|
|
85
|
+
export class SwitchbladeSectionController {
|
|
86
|
+
/** Snapshot store backing the section's view state. */
|
|
87
|
+
readonly store: SnapshotStore<SwitchbladeSectionState> = createSnapshotStore(IDLE)
|
|
88
|
+
|
|
89
|
+
constructor(
|
|
90
|
+
private readonly api: ConnectionHandle['api'],
|
|
91
|
+
private readonly sessionId?: () => SessionId | undefined,
|
|
92
|
+
) {}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Load skills, presets, prompts, and installed skills. Prompts and installed
|
|
96
|
+
* skills come from the `switchblade` settings namespace (the Host watches
|
|
97
|
+
* it and re-injects on change).
|
|
98
|
+
*/
|
|
99
|
+
async load(): Promise<void> {
|
|
100
|
+
this.store.set({ ...IDLE, status: 'loading' })
|
|
101
|
+
try {
|
|
102
|
+
// skill.list requires a live session; without one we skip it (never
|
|
103
|
+
// hang). Presets + settings always resolve, so the panel opens reliably.
|
|
104
|
+
const sessionId = this.sessionId?.()
|
|
105
|
+
const calls: Promise<unknown>[] = [
|
|
106
|
+
this.api.agentPresets.list({}),
|
|
107
|
+
this.api.settings.describe({}),
|
|
108
|
+
]
|
|
109
|
+
if (sessionId !== undefined) calls.push(this.api.skills.list({ sessionId }))
|
|
110
|
+
const [presetRes, settingsRes, skillRes] = await Promise.all(calls) as [
|
|
111
|
+
Awaited<ReturnType<ConnectionHandle['api']['agentPresets']['list']>>,
|
|
112
|
+
Awaited<ReturnType<ConnectionHandle['api']['settings']['describe']>>,
|
|
113
|
+
Awaited<ReturnType<ConnectionHandle['api']['skills']['list']>> | undefined,
|
|
114
|
+
]
|
|
115
|
+
if (!presetRes.result.ok) throw new Error(`agentPreset.list: ${presetRes.result.error.message}`)
|
|
116
|
+
if (!settingsRes.result.ok) throw new Error(`settings.describe: ${settingsRes.result.error.message}`)
|
|
117
|
+
|
|
118
|
+
const skills: SkillRow[] = skillRes !== undefined && skillRes.result.ok
|
|
119
|
+
? skillRes.result.value.skills.map((skill) => ({
|
|
120
|
+
name: skill.name,
|
|
121
|
+
description: skill.description,
|
|
122
|
+
modelInvocable: skill.modelInvocable,
|
|
123
|
+
}))
|
|
124
|
+
: []
|
|
125
|
+
|
|
126
|
+
const presets: PresetRow[] = presetRes.result.value.presets.map((preset) => ({
|
|
127
|
+
id: preset.id,
|
|
128
|
+
isDefault: preset.isDefault,
|
|
129
|
+
trust: preset.trust,
|
|
130
|
+
...preset.name === undefined ? {} : { name: preset.name },
|
|
131
|
+
...preset.description === undefined ? {} : { description: preset.description },
|
|
132
|
+
...preset.broken === undefined ? {} : { broken: preset.broken },
|
|
133
|
+
}))
|
|
134
|
+
|
|
135
|
+
const switchbladeSection = this.sectionFromSettings(settingsRes.result.value, 'switchblade')
|
|
136
|
+
const prompts: PromptRow[] = Array.isArray(switchbladeSection?.prompts) ? switchbladeSection.prompts : []
|
|
137
|
+
const installedSkills: InstalledSkillRow[] = Array.isArray(switchbladeSection?.installedSkills)
|
|
138
|
+
? switchbladeSection.installedSkills.map((s: { name?: string; description?: string; content?: string; enabled?: boolean }) => ({
|
|
139
|
+
name: s.name ?? '',
|
|
140
|
+
description: s.description ?? '',
|
|
141
|
+
content: s.content ?? '',
|
|
142
|
+
enabled: s.enabled ?? true,
|
|
143
|
+
}))
|
|
144
|
+
: []
|
|
145
|
+
|
|
146
|
+
this.store.set({
|
|
147
|
+
status: 'ready',
|
|
148
|
+
skills,
|
|
149
|
+
presets,
|
|
150
|
+
commands: [],
|
|
151
|
+
prompts,
|
|
152
|
+
installedSkills,
|
|
153
|
+
})
|
|
154
|
+
} catch (error) {
|
|
155
|
+
this.store.set({ ...IDLE, status: 'error', message: messageOf(error) })
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Read one namespace's user section from a settings.describe value. */
|
|
160
|
+
private sectionFromSettings(value: unknown, ns: string): Record<string, unknown> | undefined {
|
|
161
|
+
if (typeof value !== 'object' || value === null) return undefined
|
|
162
|
+
const entries = (value as { namespaces?: unknown }).namespaces
|
|
163
|
+
if (!Array.isArray(entries)) return undefined
|
|
164
|
+
for (const entry of entries) {
|
|
165
|
+
const row = entry as { ns?: unknown; value?: unknown }
|
|
166
|
+
if (row.ns === ns) {
|
|
167
|
+
const section = row.value
|
|
168
|
+
return typeof section === 'object' && section !== null ? section as Record<string, unknown> : undefined
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
return undefined
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// ---------------------------------------------------------------------------
|
|
175
|
+
// Prompt CRUD (writes to the switchblade settings namespace; Host re-injects)
|
|
176
|
+
// ---------------------------------------------------------------------------
|
|
177
|
+
|
|
178
|
+
/** Add a prompt. */
|
|
179
|
+
async addPrompt(input: { name: string; description: string; content: string }): Promise<void> {
|
|
180
|
+
const res = await this.api.settings.mutate({
|
|
181
|
+
ns: 'switchblade',
|
|
182
|
+
ops: [{ op: 'set', path: ['prompts'], value: [...this.currentPrompts(), {
|
|
183
|
+
id: this.slugify(input.name),
|
|
184
|
+
name: input.name,
|
|
185
|
+
description: input.description,
|
|
186
|
+
content: input.content,
|
|
187
|
+
order: this.currentPrompts().length,
|
|
188
|
+
enabled: true,
|
|
189
|
+
isDefault: this.currentPrompts().length === 0,
|
|
190
|
+
}] }],
|
|
191
|
+
})
|
|
192
|
+
if (!res.result.ok) throw new Error(res.result.error.message)
|
|
193
|
+
await this.load()
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
/** Toggle one prompt's enabled state. */
|
|
197
|
+
async setPromptEnabled(id: string, enabled: boolean): Promise<void> {
|
|
198
|
+
const next = this.currentPrompts().map((p) => p.id === id ? { ...p, enabled } : p)
|
|
199
|
+
await this.writePrompts(next)
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/** Mark one prompt default; clears others. */
|
|
203
|
+
async setDefaultPrompt(id: string): Promise<void> {
|
|
204
|
+
const next = this.currentPrompts().map((p) => ({ ...p, isDefault: p.id === id }))
|
|
205
|
+
await this.writePrompts(next)
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/** Delete one prompt. */
|
|
209
|
+
async deletePrompt(id: string): Promise<void> {
|
|
210
|
+
const next = this.currentPrompts().filter((p) => p.id !== id)
|
|
211
|
+
await this.writePrompts(next)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Update a prompt's name/description/content. */
|
|
215
|
+
async updatePrompt(id: string, patch: { name?: string; description?: string; content?: string }): Promise<void> {
|
|
216
|
+
const next = this.currentPrompts().map((p) => p.id === id ? {
|
|
217
|
+
...p,
|
|
218
|
+
name: patch.name?.trim() || p.name,
|
|
219
|
+
description: patch.description ?? p.description,
|
|
220
|
+
content: patch.content ?? p.content,
|
|
221
|
+
} : p)
|
|
222
|
+
await this.writePrompts(next)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/** Persist the prompt list through the settings RPC. */
|
|
226
|
+
private async writePrompts(prompts: readonly PromptRow[]): Promise<void> {
|
|
227
|
+
const res = await this.api.settings.mutate({
|
|
228
|
+
ns: 'switchblade',
|
|
229
|
+
ops: [{ op: 'set', path: ['prompts'], value: prompts }],
|
|
230
|
+
})
|
|
231
|
+
if (!res.result.ok) throw new Error(res.result.error.message)
|
|
232
|
+
await this.load()
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
/** Current prompt list from the loaded snapshot. */
|
|
236
|
+
private currentPrompts(): readonly PromptRow[] {
|
|
237
|
+
return this.store.getSnapshot().prompts
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/** Sluggify a name into an id. */
|
|
241
|
+
private slugify(value: string): string {
|
|
242
|
+
const slug = value.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')
|
|
243
|
+
return slug.length > 0 ? slug : `prompt-${Date.now()}`
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ---------------------------------------------------------------------------
|
|
247
|
+
// Skill install/uninstall (writes to the switchblade settings namespace)
|
|
248
|
+
// ---------------------------------------------------------------------------
|
|
249
|
+
|
|
250
|
+
/** Install a skill from a name + content (enabled by default). */
|
|
251
|
+
async installSkill(input: { name: string; description: string; content: string }): Promise<void> {
|
|
252
|
+
const next = [...this.currentInstalledSkills(), {
|
|
253
|
+
name: input.name,
|
|
254
|
+
description: input.description,
|
|
255
|
+
content: input.content,
|
|
256
|
+
enabled: true,
|
|
257
|
+
}]
|
|
258
|
+
const res = await this.api.settings.mutate({
|
|
259
|
+
ns: 'switchblade',
|
|
260
|
+
ops: [{ op: 'set', path: ['installedSkills'], value: next }],
|
|
261
|
+
})
|
|
262
|
+
if (!res.result.ok) throw new Error(res.result.error.message)
|
|
263
|
+
await this.load()
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Toggle one installed skill's enabled state. */
|
|
267
|
+
async setSkillEnabled(name: string, enabled: boolean): Promise<void> {
|
|
268
|
+
const next = this.currentInstalledSkills().map((s) => s.name === name ? { ...s, enabled } : s)
|
|
269
|
+
const res = await this.api.settings.mutate({
|
|
270
|
+
ns: 'switchblade',
|
|
271
|
+
ops: [{ op: 'set', path: ['installedSkills'], value: next }],
|
|
272
|
+
})
|
|
273
|
+
if (!res.result.ok) throw new Error(res.result.error.message)
|
|
274
|
+
await this.load()
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/** Uninstall one installed skill. */
|
|
278
|
+
async uninstallSkill(name: string): Promise<void> {
|
|
279
|
+
const next = this.currentInstalledSkills().filter((s) => s.name !== name)
|
|
280
|
+
const res = await this.api.settings.mutate({
|
|
281
|
+
ns: 'switchblade',
|
|
282
|
+
ops: [{ op: 'set', path: ['installedSkills'], value: next }],
|
|
283
|
+
})
|
|
284
|
+
if (!res.result.ok) throw new Error(res.result.error.message)
|
|
285
|
+
await this.load()
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
/** Update an installed skill's name/description/content. */
|
|
289
|
+
async updateSkill(name: string, patch: { name?: string; description?: string; content?: string }): Promise<void> {
|
|
290
|
+
const next = this.currentInstalledSkills().map((s) => s.name === name ? {
|
|
291
|
+
...s,
|
|
292
|
+
name: patch.name?.trim() || s.name,
|
|
293
|
+
description: patch.description ?? s.description,
|
|
294
|
+
content: patch.content ?? s.content,
|
|
295
|
+
} : s)
|
|
296
|
+
const res = await this.api.settings.mutate({
|
|
297
|
+
ns: 'switchblade',
|
|
298
|
+
ops: [{ op: 'set', path: ['installedSkills'], value: next }],
|
|
299
|
+
})
|
|
300
|
+
if (!res.result.ok) throw new Error(res.result.error.message)
|
|
301
|
+
await this.load()
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/** Current installed skills from the loaded snapshot. */
|
|
305
|
+
private currentInstalledSkills(): readonly InstalledSkillRow[] {
|
|
306
|
+
return this.store.getSnapshot().installedSkills
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
/**
|
|
310
|
+
* Queue a zip archive (base64) for extraction into ~/.dsh/skills. The Host
|
|
311
|
+
* watch sees pendingZip and installs it (skil-filesystem then discovers it).
|
|
312
|
+
*/
|
|
313
|
+
async installSkillFromZip(name: string, dataBase64: string): Promise<void> {
|
|
314
|
+
const res = await this.api.settings.mutate({
|
|
315
|
+
ns: 'switchblade',
|
|
316
|
+
ops: [{ op: 'set', path: ['pendingZip'], value: { name, dataBase64 } }],
|
|
317
|
+
})
|
|
318
|
+
if (!res.result.ok) throw new Error(res.result.error.message)
|
|
319
|
+
// Wait a tick for the Host watch to extract, then refresh.
|
|
320
|
+
await new Promise((r) => setTimeout(r, 500))
|
|
321
|
+
await this.load()
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
/** Set the default prompt preset. */
|
|
325
|
+
async setDefaultPreset(id: string): Promise<void> {
|
|
326
|
+
const res = await this.api.settings.update({ ns: 'agent-presets', patch: { default: id } })
|
|
327
|
+
if (!res.result.ok) throw new Error(res.result.error.message)
|
|
328
|
+
await this.load()
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/** The section's injected face: hooks (snapshot store) + actions. */
|
|
333
|
+
export interface SwitchbladeSectionInjected {
|
|
334
|
+
hooks: {
|
|
335
|
+
/** Page snapshot bound by the renderer as useSwitchblade. */
|
|
336
|
+
switchblade: SnapshotStore<SwitchbladeSectionState>
|
|
337
|
+
}
|
|
338
|
+
load: () => Promise<void>
|
|
339
|
+
setDefaultPreset: (id: string) => Promise<void>
|
|
340
|
+
addPrompt: (input: { name: string; description: string; content: string }) => Promise<void>
|
|
341
|
+
updatePrompt: (id: string, patch: { name?: string; description?: string; content?: string }) => Promise<void>
|
|
342
|
+
setPromptEnabled: (id: string, enabled: boolean) => Promise<void>
|
|
343
|
+
setDefaultPrompt: (id: string) => Promise<void>
|
|
344
|
+
deletePrompt: (id: string) => Promise<void>
|
|
345
|
+
installSkill: (input: { name: string; description: string; content: string }) => Promise<void>
|
|
346
|
+
updateSkill: (name: string, patch: { name?: string; description?: string; content?: string }) => Promise<void>
|
|
347
|
+
setSkillEnabled: (name: string, enabled: boolean) => Promise<void>
|
|
348
|
+
uninstallSkill: (name: string) => Promise<void>
|
|
349
|
+
installSkillFromZip: (name: string, dataBase64: string) => Promise<void>
|
|
350
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Switchblade management page, node half. Pure UI plugin: the empty apply
|
|
3
|
+
* exists so the plugin appears in the host cordis.yml / Loader; the browser
|
|
4
|
+
* half ships via exports["./client"], discovered through the package.json
|
|
5
|
+
* dsh.client declaration.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Host plugin body — no host-side behavior for this UI plugin. */
|
|
9
|
+
export function apply(): void {}
|
|
10
|
+
|
|
11
|
+
/** Cordis plugin identity (host face). */
|
|
12
|
+
export const name = 'ui-switchblade'
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validation invariants for `@deepseek-ai/dsh-client-ui-switchblade`.
|
|
3
|
+
* @module @deepseek-ai/dsh-client-ui-switchblade
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/** The settings.section id this package registers. */
|
|
7
|
+
export const SECTION_ID = 'switchblade'
|
|
8
|
+
|
|
9
|
+
/** Display order in the settings nav (after Models/Agent Presets). */
|
|
10
|
+
export const SECTION_ORDER = 30
|