my-pi 0.0.2 → 0.0.4
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/README.md +24 -5
- package/dist/api-B6KnhtN9.js +1893 -0
- package/dist/api-B6KnhtN9.js.map +1 -0
- package/dist/api.js +1 -49
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
- package/src/extensions/config.test.ts +88 -0
- package/src/extensions/config.ts +189 -0
- package/src/extensions/extensions.ts +366 -0
- package/src/extensions/recall.ts +29 -226
- package/src/extensions/skills.ts +496 -75
- package/src/skills/importer.test.ts +301 -0
- package/src/skills/importer.ts +221 -0
- package/src/skills/manager.ts +129 -30
- package/src/skills/scanner.ts +172 -72
- package/dist/api.js.map +0 -1
package/src/extensions/skills.ts
CHANGED
|
@@ -1,27 +1,194 @@
|
|
|
1
1
|
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
Container,
|
|
4
|
+
SettingsList,
|
|
5
|
+
Text,
|
|
6
|
+
type SettingItem,
|
|
7
|
+
} from '@mariozechner/pi-tui';
|
|
8
|
+
import {
|
|
9
|
+
create_skills_manager,
|
|
10
|
+
type ManagedSkill,
|
|
11
|
+
} from '../skills/manager.js';
|
|
12
|
+
|
|
13
|
+
const ENABLED = '[x]';
|
|
14
|
+
const DISABLED = '[ ]';
|
|
15
|
+
const SYNC = '[~]';
|
|
16
|
+
const IMPORTED_LABEL = '[=]';
|
|
17
|
+
|
|
18
|
+
function sort_skills(skills: ManagedSkill[]): ManagedSkill[] {
|
|
19
|
+
return [...skills].sort((a, b) => {
|
|
20
|
+
const by_name = a.name.localeCompare(b.name);
|
|
21
|
+
if (by_name !== 0) return by_name;
|
|
22
|
+
const by_source = a.source.localeCompare(b.source);
|
|
23
|
+
if (by_source !== 0) return by_source;
|
|
24
|
+
return a.key.localeCompare(b.key);
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function find_matching_imported_skill(
|
|
29
|
+
managed_skills: ManagedSkill[],
|
|
30
|
+
skill: ManagedSkill,
|
|
31
|
+
): ManagedSkill | undefined {
|
|
32
|
+
const exact_match = managed_skills.find(
|
|
33
|
+
(candidate) =>
|
|
34
|
+
candidate.import_meta?.source === skill.source &&
|
|
35
|
+
(candidate.import_meta.upstream_skill_path ===
|
|
36
|
+
skill.skillPath ||
|
|
37
|
+
candidate.import_meta.upstream_base_dir === skill.baseDir),
|
|
38
|
+
);
|
|
39
|
+
if (exact_match) return exact_match;
|
|
40
|
+
|
|
41
|
+
return managed_skills.find(
|
|
42
|
+
(candidate) =>
|
|
43
|
+
candidate.import_meta?.source === skill.source &&
|
|
44
|
+
candidate.name === skill.name,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function get_importable_state(
|
|
49
|
+
managed_skills: ManagedSkill[],
|
|
50
|
+
skill: ManagedSkill,
|
|
51
|
+
): {
|
|
52
|
+
label: string;
|
|
53
|
+
detail: string;
|
|
54
|
+
action: 'import' | 'sync' | null;
|
|
55
|
+
} {
|
|
56
|
+
const imported = find_matching_imported_skill(
|
|
57
|
+
managed_skills,
|
|
58
|
+
skill,
|
|
59
|
+
);
|
|
60
|
+
if (imported?.import_meta) {
|
|
61
|
+
const version_changed = Boolean(
|
|
62
|
+
skill.plugin?.version &&
|
|
63
|
+
imported.import_meta.upstream_version &&
|
|
64
|
+
skill.plugin.version !== imported.import_meta.upstream_version,
|
|
65
|
+
);
|
|
66
|
+
const sha_changed = Boolean(
|
|
67
|
+
skill.plugin?.gitCommitSha &&
|
|
68
|
+
imported.import_meta.upstream_git_commit_sha &&
|
|
69
|
+
skill.plugin.gitCommitSha !==
|
|
70
|
+
imported.import_meta.upstream_git_commit_sha,
|
|
71
|
+
);
|
|
72
|
+
|
|
73
|
+
if (version_changed || sha_changed) {
|
|
74
|
+
return {
|
|
75
|
+
label: 'sync',
|
|
76
|
+
detail: 'Press Enter to sync the imported copy and reload',
|
|
77
|
+
action: 'sync',
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return {
|
|
82
|
+
label: 'imported',
|
|
83
|
+
detail: `Already imported to ${imported.baseDir}`,
|
|
84
|
+
action: null,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const managed_conflict = managed_skills.find(
|
|
89
|
+
(candidate) => candidate.name === skill.name,
|
|
90
|
+
);
|
|
91
|
+
if (managed_conflict) {
|
|
92
|
+
return {
|
|
93
|
+
label: 'managed',
|
|
94
|
+
detail: `Already managed at ${managed_conflict.baseDir}`,
|
|
95
|
+
action: null,
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
return {
|
|
100
|
+
label: 'import',
|
|
101
|
+
detail: 'Press Enter to import into pi-native skills and reload',
|
|
102
|
+
action: 'import',
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function to_setting_item(skill: ManagedSkill): SettingItem {
|
|
107
|
+
const detail_lines = [
|
|
108
|
+
`${skill.source} • ${skill.key}`,
|
|
109
|
+
skill.description,
|
|
110
|
+
skill.baseDir,
|
|
111
|
+
];
|
|
112
|
+
if (skill.import_meta?.upstream_version) {
|
|
113
|
+
detail_lines.push(
|
|
114
|
+
`upstream: ${skill.import_meta.upstream_version}${skill.import_meta.upstream_git_commit_sha ? ` • ${skill.import_meta.upstream_git_commit_sha.slice(0, 12)}` : ''}`,
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
id: skill.key,
|
|
120
|
+
label: skill.name,
|
|
121
|
+
description: detail_lines.join('\n'),
|
|
122
|
+
currentValue: skill.enabled ? ENABLED : DISABLED,
|
|
123
|
+
values: [ENABLED, DISABLED],
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function to_importable_setting_item(
|
|
128
|
+
managed_skills: ManagedSkill[],
|
|
129
|
+
skill: ManagedSkill,
|
|
130
|
+
): SettingItem {
|
|
131
|
+
const state = get_importable_state(managed_skills, skill);
|
|
132
|
+
const detail_lines = [
|
|
133
|
+
`${skill.source} • ${skill.key}`,
|
|
134
|
+
skill.description,
|
|
135
|
+
skill.baseDir,
|
|
136
|
+
];
|
|
137
|
+
if (skill.plugin?.version) {
|
|
138
|
+
detail_lines.push(
|
|
139
|
+
`plugin: ${skill.plugin.version}${skill.plugin.gitCommitSha ? ` • ${skill.plugin.gitCommitSha.slice(0, 12)}` : ''}`,
|
|
140
|
+
);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (state.action === 'import') {
|
|
144
|
+
return {
|
|
145
|
+
id: skill.key,
|
|
146
|
+
label: skill.name,
|
|
147
|
+
description: detail_lines.join('\n'),
|
|
148
|
+
currentValue: DISABLED,
|
|
149
|
+
values: [ENABLED, DISABLED],
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (state.action === 'sync') {
|
|
154
|
+
detail_lines.push('enter to sync');
|
|
155
|
+
return {
|
|
156
|
+
id: skill.key,
|
|
157
|
+
label: skill.name,
|
|
158
|
+
description: detail_lines.join('\n'),
|
|
159
|
+
currentValue: SYNC,
|
|
160
|
+
values: [SYNC],
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
detail_lines.push(state.detail);
|
|
165
|
+
return {
|
|
166
|
+
id: skill.key,
|
|
167
|
+
label: skill.name,
|
|
168
|
+
description: detail_lines.join('\n'),
|
|
169
|
+
currentValue: IMPORTED_LABEL,
|
|
170
|
+
};
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function sets_equal(
|
|
174
|
+
a: ReadonlySet<string>,
|
|
175
|
+
b: ReadonlySet<string>,
|
|
176
|
+
): boolean {
|
|
177
|
+
if (a.size !== b.size) return false;
|
|
178
|
+
for (const value of a) {
|
|
179
|
+
if (!b.has(value)) return false;
|
|
180
|
+
}
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
3
183
|
|
|
4
184
|
// Default export for Pi Package / additionalExtensionPaths loading
|
|
5
185
|
export default async function skills(pi: ExtensionAPI) {
|
|
6
186
|
const mgr = create_skills_manager();
|
|
7
187
|
|
|
8
|
-
|
|
9
|
-
pi.on('resources_discover', () => ({
|
|
10
|
-
skillPaths: mgr.get_enabled_skill_paths(),
|
|
11
|
-
}));
|
|
12
|
-
|
|
13
|
-
const subs = [
|
|
14
|
-
'discover',
|
|
15
|
-
'enable',
|
|
16
|
-
'disable',
|
|
17
|
-
'toggle',
|
|
18
|
-
'search',
|
|
19
|
-
'refresh',
|
|
20
|
-
'defaults',
|
|
21
|
-
];
|
|
188
|
+
const subs = ['import', 'sync', 'refresh', 'defaults'];
|
|
22
189
|
|
|
23
190
|
pi.registerCommand('skills', {
|
|
24
|
-
description: '
|
|
191
|
+
description: 'Manage pi-native skills and import external skills',
|
|
25
192
|
getArgumentCompletions: (prefix) => {
|
|
26
193
|
const parts = prefix.trim().split(/\s+/);
|
|
27
194
|
if (parts.length <= 1) {
|
|
@@ -29,94 +196,348 @@ export default async function skills(pi: ExtensionAPI) {
|
|
|
29
196
|
.filter((s) => s.startsWith(parts[0] || ''))
|
|
30
197
|
.map((s) => ({ value: s, label: s }));
|
|
31
198
|
}
|
|
32
|
-
|
|
199
|
+
|
|
200
|
+
if (parts[0] === 'import') {
|
|
33
201
|
const q = parts.slice(1).join(' ').toLowerCase();
|
|
34
|
-
return mgr
|
|
35
|
-
.
|
|
36
|
-
|
|
202
|
+
return sort_skills(mgr.discover_importable())
|
|
203
|
+
.filter(
|
|
204
|
+
(s) =>
|
|
205
|
+
s.key.toLowerCase().includes(q) ||
|
|
206
|
+
s.name.toLowerCase().includes(q),
|
|
207
|
+
)
|
|
37
208
|
.slice(0, 20)
|
|
38
209
|
.map((s) => ({
|
|
39
210
|
value: `${parts[0]} ${s.key}`,
|
|
40
|
-
label:
|
|
211
|
+
label: s.key,
|
|
41
212
|
}));
|
|
42
213
|
}
|
|
214
|
+
|
|
215
|
+
if (parts[0] === 'sync') {
|
|
216
|
+
const q = parts.slice(1).join(' ').toLowerCase();
|
|
217
|
+
return sort_skills(
|
|
218
|
+
mgr
|
|
219
|
+
.discover()
|
|
220
|
+
.filter((skill) => Boolean(skill.import_meta)),
|
|
221
|
+
)
|
|
222
|
+
.filter(
|
|
223
|
+
(s) =>
|
|
224
|
+
s.key.toLowerCase().includes(q) ||
|
|
225
|
+
s.name.toLowerCase().includes(q),
|
|
226
|
+
)
|
|
227
|
+
.slice(0, 20)
|
|
228
|
+
.map((s) => ({
|
|
229
|
+
value: `${parts[0]} ${s.key}`,
|
|
230
|
+
label: s.key,
|
|
231
|
+
}));
|
|
232
|
+
}
|
|
233
|
+
|
|
43
234
|
return null;
|
|
44
235
|
},
|
|
45
236
|
handler: async (args, ctx) => {
|
|
46
|
-
const
|
|
47
|
-
const arg = rest.join(' ');
|
|
237
|
+
const trimmed = args.trim();
|
|
48
238
|
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
239
|
+
if (!trimmed && ctx.hasUI) {
|
|
240
|
+
const discovered = sort_skills(mgr.discover());
|
|
241
|
+
const importable = sort_skills(mgr.discover_importable());
|
|
242
|
+
if (discovered.length === 0 && importable.length === 0) {
|
|
243
|
+
ctx.ui.notify('No managed or importable skills found');
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const initial_enabled = new Set(
|
|
248
|
+
discovered
|
|
249
|
+
.filter((skill) => skill.enabled)
|
|
250
|
+
.map((skill) => skill.key),
|
|
251
|
+
);
|
|
252
|
+
const current_enabled = new Set(initial_enabled);
|
|
253
|
+
const queued_imports = new Set<string>();
|
|
254
|
+
let reload_notice: string | null = null;
|
|
255
|
+
|
|
256
|
+
const managed_items = discovered.map(to_setting_item);
|
|
257
|
+
const importable_items = importable.map((skill) =>
|
|
258
|
+
to_importable_setting_item(discovered, skill),
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const all_items: SettingItem[] = [];
|
|
262
|
+
if (managed_items.length > 0) {
|
|
263
|
+
all_items.push({
|
|
264
|
+
id: '__header_managed__',
|
|
265
|
+
label: `── Managed (${managed_items.length}) ──`,
|
|
266
|
+
description: '',
|
|
267
|
+
currentValue: '',
|
|
268
|
+
});
|
|
269
|
+
all_items.push(...managed_items);
|
|
270
|
+
}
|
|
271
|
+
if (importable_items.length > 0) {
|
|
272
|
+
all_items.push({
|
|
273
|
+
id: '__header_importable__',
|
|
274
|
+
label: `── Importable (${importable_items.length}) ──`,
|
|
275
|
+
description: '',
|
|
276
|
+
currentValue: '',
|
|
277
|
+
});
|
|
278
|
+
all_items.push(...importable_items);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const managed_keys = new Set(discovered.map((s) => s.key));
|
|
282
|
+
const importable_map = new Map(
|
|
283
|
+
importable.map((s) => [s.key, s]),
|
|
284
|
+
);
|
|
285
|
+
|
|
286
|
+
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
287
|
+
const list = new SettingsList(
|
|
288
|
+
all_items,
|
|
289
|
+
Math.min(Math.max(all_items.length + 4, 8), 22),
|
|
290
|
+
{
|
|
291
|
+
cursor: theme.fg('accent', '›'),
|
|
292
|
+
label: (text, selected) => {
|
|
293
|
+
if (text.startsWith('──') && text.endsWith('──')) {
|
|
294
|
+
return theme.fg('dim', theme.bold(text));
|
|
295
|
+
}
|
|
296
|
+
return selected ? theme.fg('accent', text) : text;
|
|
297
|
+
},
|
|
298
|
+
value: (text, selected) => {
|
|
299
|
+
const color =
|
|
300
|
+
text === ENABLED
|
|
301
|
+
? ('success' as const)
|
|
302
|
+
: text === SYNC
|
|
303
|
+
? ('warning' as const)
|
|
304
|
+
: text === IMPORTED_LABEL
|
|
305
|
+
? ('success' as const)
|
|
306
|
+
: ('dim' as const);
|
|
307
|
+
const rendered = theme.fg(color, text);
|
|
308
|
+
return selected
|
|
309
|
+
? theme.bold(theme.fg('accent', rendered))
|
|
310
|
+
: rendered;
|
|
311
|
+
},
|
|
312
|
+
description: (text) => theme.fg('muted', text),
|
|
313
|
+
hint: (text) => theme.fg('dim', text),
|
|
314
|
+
},
|
|
315
|
+
(id, new_value) => {
|
|
316
|
+
if (id.startsWith('__header_')) return;
|
|
317
|
+
|
|
318
|
+
if (managed_keys.has(id)) {
|
|
319
|
+
if (new_value === ENABLED) {
|
|
320
|
+
current_enabled.add(id);
|
|
321
|
+
mgr.enable(id);
|
|
322
|
+
} else {
|
|
323
|
+
current_enabled.delete(id);
|
|
324
|
+
mgr.disable(id);
|
|
325
|
+
}
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const import_skill = importable_map.get(id);
|
|
330
|
+
if (!import_skill) return;
|
|
331
|
+
|
|
332
|
+
const state = get_importable_state(
|
|
333
|
+
discovered,
|
|
334
|
+
import_skill,
|
|
335
|
+
);
|
|
336
|
+
|
|
337
|
+
if (state.action === 'import') {
|
|
338
|
+
if (new_value === ENABLED) {
|
|
339
|
+
queued_imports.add(id);
|
|
340
|
+
} else {
|
|
341
|
+
queued_imports.delete(id);
|
|
342
|
+
}
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
if (state.action === 'sync') {
|
|
347
|
+
const imported_skill = find_matching_imported_skill(
|
|
348
|
+
discovered,
|
|
349
|
+
import_skill,
|
|
350
|
+
);
|
|
351
|
+
if (!imported_skill) {
|
|
352
|
+
ctx.ui.notify(
|
|
353
|
+
`Imported copy for ${import_skill.name} was not found`,
|
|
354
|
+
'warning',
|
|
355
|
+
);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
try {
|
|
359
|
+
const result = mgr.sync_skill(imported_skill.key);
|
|
360
|
+
if (result.changed) {
|
|
361
|
+
reload_notice = `Synced ${import_skill.name}. Reloading...`;
|
|
362
|
+
done(undefined);
|
|
363
|
+
} else {
|
|
364
|
+
ctx.ui.notify(
|
|
365
|
+
`${import_skill.name} is already up to date.`,
|
|
366
|
+
'info',
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
} catch (error) {
|
|
370
|
+
ctx.ui.notify(
|
|
371
|
+
error instanceof Error
|
|
372
|
+
? error.message
|
|
373
|
+
: String(error),
|
|
374
|
+
'warning',
|
|
375
|
+
);
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
},
|
|
379
|
+
() => done(undefined),
|
|
380
|
+
{ enableSearch: true },
|
|
381
|
+
);
|
|
382
|
+
|
|
383
|
+
const container = new Container();
|
|
384
|
+
|
|
385
|
+
container.addChild({
|
|
386
|
+
render: () => {
|
|
387
|
+
const enabled = current_enabled.size;
|
|
388
|
+
const disabled = discovered.length - enabled;
|
|
389
|
+
const queued = queued_imports.size;
|
|
390
|
+
const parts = [
|
|
391
|
+
`${enabled} enabled`,
|
|
392
|
+
`${disabled} disabled`,
|
|
393
|
+
];
|
|
394
|
+
if (importable.length > 0) {
|
|
395
|
+
parts.push(`${importable.length} importable`);
|
|
396
|
+
}
|
|
397
|
+
if (queued > 0) {
|
|
398
|
+
parts.push(`${queued} queued for import`);
|
|
399
|
+
}
|
|
400
|
+
return [
|
|
401
|
+
theme.fg('accent', theme.bold('Skills')),
|
|
402
|
+
theme.fg('muted', parts.join(' • ')),
|
|
403
|
+
'',
|
|
404
|
+
];
|
|
405
|
+
},
|
|
406
|
+
invalidate: () => {},
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
container.addChild({
|
|
410
|
+
render(width: number) {
|
|
411
|
+
return list.render(width);
|
|
412
|
+
},
|
|
413
|
+
invalidate() {
|
|
414
|
+
list.invalidate();
|
|
415
|
+
},
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
container.addChild(
|
|
419
|
+
new Text(
|
|
420
|
+
theme.fg(
|
|
421
|
+
'dim',
|
|
422
|
+
'search filters • enter toggles • esc close',
|
|
423
|
+
),
|
|
424
|
+
0,
|
|
425
|
+
1,
|
|
426
|
+
),
|
|
427
|
+
);
|
|
428
|
+
|
|
429
|
+
return {
|
|
430
|
+
render(width: number) {
|
|
431
|
+
return container.render(width);
|
|
432
|
+
},
|
|
433
|
+
invalidate() {
|
|
434
|
+
container.invalidate();
|
|
435
|
+
},
|
|
436
|
+
handleInput(data: string) {
|
|
437
|
+
list.handleInput(data);
|
|
438
|
+
tui.requestRender();
|
|
439
|
+
},
|
|
440
|
+
};
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
if (queued_imports.size > 0) {
|
|
444
|
+
const imported_names: string[] = [];
|
|
445
|
+
for (const key of queued_imports) {
|
|
446
|
+
try {
|
|
447
|
+
mgr.import_skill(key);
|
|
448
|
+
imported_names.push(key);
|
|
449
|
+
} catch (error) {
|
|
450
|
+
ctx.ui.notify(
|
|
451
|
+
error instanceof Error
|
|
452
|
+
? error.message
|
|
453
|
+
: String(error),
|
|
454
|
+
'warning',
|
|
455
|
+
);
|
|
456
|
+
}
|
|
55
457
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
const lines: string[] = [
|
|
59
|
-
`${skills.length} skills (${on} enabled, ${off} disabled)\n`,
|
|
60
|
-
];
|
|
61
|
-
for (const s of skills) {
|
|
62
|
-
lines.push(` ${s.enabled ? '+' : '-'} ${s.key}`);
|
|
63
|
-
lines.push(` ${s.description.slice(0, 80)}`);
|
|
458
|
+
if (imported_names.length > 0) {
|
|
459
|
+
reload_notice = `Imported ${imported_names.length} skill(s). Reloading...`;
|
|
64
460
|
}
|
|
65
|
-
ctx.ui.notify(lines.join('\n'));
|
|
66
|
-
break;
|
|
67
461
|
}
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
mgr.enable(arg);
|
|
74
|
-
ctx.ui.notify(`Enabled ${arg}. /reload to apply.`);
|
|
75
|
-
break;
|
|
462
|
+
|
|
463
|
+
if (reload_notice) {
|
|
464
|
+
ctx.ui.notify(reload_notice, 'info');
|
|
465
|
+
await ctx.reload();
|
|
466
|
+
return;
|
|
76
467
|
}
|
|
77
|
-
|
|
468
|
+
|
|
469
|
+
if (!sets_equal(initial_enabled, current_enabled)) {
|
|
470
|
+
ctx.ui.notify(
|
|
471
|
+
'Reloading to apply updated skills...',
|
|
472
|
+
'info',
|
|
473
|
+
);
|
|
474
|
+
await ctx.reload();
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
return;
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
const [sub, ...rest] = (trimmed || 'list').split(/\s+/);
|
|
482
|
+
const arg = rest.join(' ');
|
|
483
|
+
|
|
484
|
+
switch (sub) {
|
|
485
|
+
case 'import': {
|
|
78
486
|
if (!arg) {
|
|
79
|
-
ctx.ui.notify(
|
|
487
|
+
ctx.ui.notify(
|
|
488
|
+
'Usage: /skills import <key|name>',
|
|
489
|
+
'warning',
|
|
490
|
+
);
|
|
80
491
|
return;
|
|
81
492
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
ctx.
|
|
493
|
+
try {
|
|
494
|
+
const result = mgr.import_skill(arg);
|
|
495
|
+
ctx.ui.notify(
|
|
496
|
+
`Imported ${arg} to ${result.skillDir}. Reloading...`,
|
|
497
|
+
'info',
|
|
498
|
+
);
|
|
499
|
+
await ctx.reload();
|
|
500
|
+
return;
|
|
501
|
+
} catch (error) {
|
|
502
|
+
ctx.ui.notify(
|
|
503
|
+
error instanceof Error ? error.message : String(error),
|
|
504
|
+
'warning',
|
|
505
|
+
);
|
|
89
506
|
return;
|
|
90
507
|
}
|
|
91
|
-
const state = mgr.toggle(arg);
|
|
92
|
-
ctx.ui.notify(
|
|
93
|
-
`${arg} ${state ? 'enabled' : 'disabled'}. /reload to apply.`,
|
|
94
|
-
);
|
|
95
|
-
break;
|
|
96
508
|
}
|
|
97
|
-
case '
|
|
509
|
+
case 'sync': {
|
|
98
510
|
if (!arg) {
|
|
99
|
-
ctx.ui.notify(
|
|
511
|
+
ctx.ui.notify(
|
|
512
|
+
'Usage: /skills sync <key|name>',
|
|
513
|
+
'warning',
|
|
514
|
+
);
|
|
100
515
|
return;
|
|
101
516
|
}
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
ctx.ui.notify(
|
|
517
|
+
try {
|
|
518
|
+
const result = mgr.sync_skill(arg);
|
|
519
|
+
ctx.ui.notify(
|
|
520
|
+
result.changed
|
|
521
|
+
? `Synced ${arg}. Reloading...`
|
|
522
|
+
: `${arg} is already up to date.`,
|
|
523
|
+
'info',
|
|
524
|
+
);
|
|
525
|
+
if (result.changed) {
|
|
526
|
+
await ctx.reload();
|
|
527
|
+
}
|
|
528
|
+
return;
|
|
529
|
+
} catch (error) {
|
|
530
|
+
ctx.ui.notify(
|
|
531
|
+
error instanceof Error ? error.message : String(error),
|
|
532
|
+
'warning',
|
|
533
|
+
);
|
|
105
534
|
return;
|
|
106
535
|
}
|
|
107
|
-
const lines = results.map(
|
|
108
|
-
(s) =>
|
|
109
|
-
`${s.enabled ? '+' : '-'} ${s.key}\n ${s.description.slice(0, 80)}`,
|
|
110
|
-
);
|
|
111
|
-
ctx.ui.notify(
|
|
112
|
-
`${results.length} matches:\n ${lines.join('\n ')}`,
|
|
113
|
-
);
|
|
114
|
-
break;
|
|
115
536
|
}
|
|
116
537
|
case 'refresh': {
|
|
117
538
|
mgr.refresh();
|
|
118
539
|
ctx.ui.notify(
|
|
119
|
-
`Rescanned: ${mgr.discover().length} skills found`,
|
|
540
|
+
`Rescanned: ${mgr.discover().length} managed skills, ${mgr.discover_importable().length} importable skills found`,
|
|
120
541
|
);
|
|
121
542
|
break;
|
|
122
543
|
}
|