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
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import {
|
|
2
|
+
existsSync,
|
|
3
|
+
mkdirSync,
|
|
4
|
+
readFileSync,
|
|
5
|
+
renameSync,
|
|
6
|
+
writeFileSync,
|
|
7
|
+
} from 'node:fs';
|
|
8
|
+
import { homedir } from 'node:os';
|
|
9
|
+
import { dirname, join } from 'node:path';
|
|
10
|
+
|
|
11
|
+
export type BuiltinExtensionKey =
|
|
12
|
+
| 'mcp'
|
|
13
|
+
| 'skills'
|
|
14
|
+
| 'chain'
|
|
15
|
+
| 'filter-output'
|
|
16
|
+
| 'handoff'
|
|
17
|
+
| 'recall';
|
|
18
|
+
|
|
19
|
+
export interface BuiltinExtensionInfo {
|
|
20
|
+
key: BuiltinExtensionKey;
|
|
21
|
+
label: string;
|
|
22
|
+
description: string;
|
|
23
|
+
cli_flag: string;
|
|
24
|
+
aliases: string[];
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface BuiltinExtensionsConfig {
|
|
28
|
+
version: number;
|
|
29
|
+
enabled: Partial<Record<BuiltinExtensionKey, boolean>>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface BuiltinExtensionState extends BuiltinExtensionInfo {
|
|
33
|
+
saved_enabled: boolean;
|
|
34
|
+
effective_enabled: boolean;
|
|
35
|
+
forced_disabled: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const DEFAULT_CONFIG: BuiltinExtensionsConfig = {
|
|
39
|
+
version: 1,
|
|
40
|
+
enabled: {},
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export const BUILTIN_EXTENSIONS: BuiltinExtensionInfo[] = [
|
|
44
|
+
{
|
|
45
|
+
key: 'mcp',
|
|
46
|
+
label: 'MCP',
|
|
47
|
+
description: 'MCP server integration and /mcp command',
|
|
48
|
+
cli_flag: '--no-mcp',
|
|
49
|
+
aliases: ['mcp'],
|
|
50
|
+
},
|
|
51
|
+
{
|
|
52
|
+
key: 'skills',
|
|
53
|
+
label: 'Skills',
|
|
54
|
+
description: 'Managed pi-native skills and /skills command',
|
|
55
|
+
cli_flag: '--no-skills',
|
|
56
|
+
aliases: ['skills', 'skill'],
|
|
57
|
+
},
|
|
58
|
+
{
|
|
59
|
+
key: 'chain',
|
|
60
|
+
label: 'Chain',
|
|
61
|
+
description: 'Agent chain orchestration and /chain command',
|
|
62
|
+
cli_flag: '--no-chain',
|
|
63
|
+
aliases: ['chain', 'chains'],
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
key: 'filter-output',
|
|
67
|
+
label: 'Filter output',
|
|
68
|
+
description: 'Secret redaction for tool output',
|
|
69
|
+
cli_flag: '--no-filter',
|
|
70
|
+
aliases: [
|
|
71
|
+
'filter-output',
|
|
72
|
+
'filter_output',
|
|
73
|
+
'filter',
|
|
74
|
+
'redaction',
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
key: 'handoff',
|
|
79
|
+
label: 'Handoff',
|
|
80
|
+
description: 'Session handoff export and /handoff command',
|
|
81
|
+
cli_flag: '--no-handoff',
|
|
82
|
+
aliases: ['handoff'],
|
|
83
|
+
},
|
|
84
|
+
{
|
|
85
|
+
key: 'recall',
|
|
86
|
+
label: 'Recall',
|
|
87
|
+
description: 'Past session recall guidance and /recall command',
|
|
88
|
+
cli_flag: '--no-recall',
|
|
89
|
+
aliases: ['recall'],
|
|
90
|
+
},
|
|
91
|
+
];
|
|
92
|
+
|
|
93
|
+
export function get_builtin_extensions_config_path(): string {
|
|
94
|
+
const xdg =
|
|
95
|
+
process.env.XDG_CONFIG_HOME || join(homedir(), '.config');
|
|
96
|
+
return join(xdg, 'my-pi', 'extensions.json');
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function load_builtin_extensions_config(): BuiltinExtensionsConfig {
|
|
100
|
+
const path = get_builtin_extensions_config_path();
|
|
101
|
+
if (!existsSync(path)) return { ...DEFAULT_CONFIG };
|
|
102
|
+
|
|
103
|
+
try {
|
|
104
|
+
const raw = readFileSync(path, 'utf-8');
|
|
105
|
+
const parsed = JSON.parse(
|
|
106
|
+
raw,
|
|
107
|
+
) as Partial<BuiltinExtensionsConfig>;
|
|
108
|
+
const enabled: BuiltinExtensionsConfig['enabled'] = {};
|
|
109
|
+
for (const extension of BUILTIN_EXTENSIONS) {
|
|
110
|
+
const value = parsed.enabled?.[extension.key];
|
|
111
|
+
if (typeof value === 'boolean') {
|
|
112
|
+
enabled[extension.key] = value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
version: parsed.version ?? 1,
|
|
118
|
+
enabled,
|
|
119
|
+
};
|
|
120
|
+
} catch {
|
|
121
|
+
return { ...DEFAULT_CONFIG };
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export function save_builtin_extensions_config(
|
|
126
|
+
config: BuiltinExtensionsConfig,
|
|
127
|
+
): void {
|
|
128
|
+
const path = get_builtin_extensions_config_path();
|
|
129
|
+
const dir = dirname(path);
|
|
130
|
+
if (!existsSync(dir)) {
|
|
131
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const tmp = `${path}.tmp-${Date.now()}`;
|
|
135
|
+
writeFileSync(tmp, JSON.stringify(config, null, '\t') + '\n', {
|
|
136
|
+
mode: 0o600,
|
|
137
|
+
});
|
|
138
|
+
renameSync(tmp, path);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
export function is_builtin_extension_enabled(
|
|
142
|
+
config: BuiltinExtensionsConfig,
|
|
143
|
+
key: BuiltinExtensionKey,
|
|
144
|
+
): boolean {
|
|
145
|
+
return config.enabled[key] ?? true;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
export function is_builtin_extension_active(
|
|
149
|
+
config: BuiltinExtensionsConfig,
|
|
150
|
+
key: BuiltinExtensionKey,
|
|
151
|
+
force_disabled: ReadonlySet<BuiltinExtensionKey> = new Set(),
|
|
152
|
+
): boolean {
|
|
153
|
+
return (
|
|
154
|
+
is_builtin_extension_enabled(config, key) &&
|
|
155
|
+
!force_disabled.has(key)
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
export function resolve_builtin_extension_states(
|
|
160
|
+
force_disabled: ReadonlySet<BuiltinExtensionKey> = new Set(),
|
|
161
|
+
config: BuiltinExtensionsConfig = load_builtin_extensions_config(),
|
|
162
|
+
): BuiltinExtensionState[] {
|
|
163
|
+
return BUILTIN_EXTENSIONS.map((extension) => {
|
|
164
|
+
const saved_enabled = is_builtin_extension_enabled(
|
|
165
|
+
config,
|
|
166
|
+
extension.key,
|
|
167
|
+
);
|
|
168
|
+
const forced = force_disabled.has(extension.key);
|
|
169
|
+
return {
|
|
170
|
+
...extension,
|
|
171
|
+
saved_enabled,
|
|
172
|
+
effective_enabled: saved_enabled && !forced,
|
|
173
|
+
forced_disabled: forced,
|
|
174
|
+
};
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
export function find_builtin_extension(
|
|
179
|
+
query: string,
|
|
180
|
+
): BuiltinExtensionInfo | undefined {
|
|
181
|
+
const normalized = query.trim().toLowerCase();
|
|
182
|
+
if (!normalized) return undefined;
|
|
183
|
+
|
|
184
|
+
return BUILTIN_EXTENSIONS.find((extension) =>
|
|
185
|
+
[extension.key, extension.label, ...extension.aliases].some(
|
|
186
|
+
(value) => value.toLowerCase() === normalized,
|
|
187
|
+
),
|
|
188
|
+
);
|
|
189
|
+
}
|
|
@@ -0,0 +1,366 @@
|
|
|
1
|
+
import type { ExtensionAPI } from '@mariozechner/pi-coding-agent';
|
|
2
|
+
import {
|
|
3
|
+
Container,
|
|
4
|
+
SettingsList,
|
|
5
|
+
Text,
|
|
6
|
+
type SettingItem,
|
|
7
|
+
} from '@mariozechner/pi-tui';
|
|
8
|
+
import {
|
|
9
|
+
BUILTIN_EXTENSIONS,
|
|
10
|
+
find_builtin_extension,
|
|
11
|
+
load_builtin_extensions_config,
|
|
12
|
+
resolve_builtin_extension_states,
|
|
13
|
+
save_builtin_extensions_config,
|
|
14
|
+
type BuiltinExtensionKey,
|
|
15
|
+
type BuiltinExtensionState,
|
|
16
|
+
} from './config.js';
|
|
17
|
+
|
|
18
|
+
const ENABLED = '[x]';
|
|
19
|
+
const DISABLED = '[ ]';
|
|
20
|
+
|
|
21
|
+
export interface ExtensionsManagerOptions {
|
|
22
|
+
force_disabled?: Iterable<BuiltinExtensionKey>;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function to_force_disabled_set(
|
|
26
|
+
force_disabled?: Iterable<BuiltinExtensionKey>,
|
|
27
|
+
): ReadonlySet<BuiltinExtensionKey> {
|
|
28
|
+
return new Set(force_disabled ?? []);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function format_effective_state(
|
|
32
|
+
state: BuiltinExtensionState,
|
|
33
|
+
): string {
|
|
34
|
+
if (state.effective_enabled) {
|
|
35
|
+
return 'enabled';
|
|
36
|
+
}
|
|
37
|
+
if (state.forced_disabled) {
|
|
38
|
+
return `disabled in this process by ${state.cli_flag}`;
|
|
39
|
+
}
|
|
40
|
+
return 'disabled';
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function format_extension_lines(
|
|
44
|
+
states: BuiltinExtensionState[],
|
|
45
|
+
options?: { heading?: string },
|
|
46
|
+
): string {
|
|
47
|
+
const lines: string[] = [];
|
|
48
|
+
if (options?.heading) {
|
|
49
|
+
lines.push(options.heading, '');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const enabled_now = states.filter(
|
|
53
|
+
(state) => state.effective_enabled,
|
|
54
|
+
).length;
|
|
55
|
+
const disabled_now = states.length - enabled_now;
|
|
56
|
+
lines.push(
|
|
57
|
+
`${states.length} built-in extensions (${enabled_now} enabled now, ${disabled_now} disabled now)`,
|
|
58
|
+
'',
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
for (const state of states) {
|
|
62
|
+
lines.push(
|
|
63
|
+
`${state.saved_enabled ? ENABLED : DISABLED} ${state.label}`,
|
|
64
|
+
);
|
|
65
|
+
lines.push(` key: ${state.key}`);
|
|
66
|
+
lines.push(
|
|
67
|
+
` saved config: ${state.saved_enabled ? 'enabled' : 'disabled'}`,
|
|
68
|
+
);
|
|
69
|
+
lines.push(
|
|
70
|
+
` current process: ${format_effective_state(state)}`,
|
|
71
|
+
);
|
|
72
|
+
lines.push(` ${state.description}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
return lines.join('\n');
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function to_setting_item(state: BuiltinExtensionState): SettingItem {
|
|
79
|
+
const detail_lines = [
|
|
80
|
+
state.key,
|
|
81
|
+
state.description,
|
|
82
|
+
`current process: ${format_effective_state(state)}`,
|
|
83
|
+
`startup override: ${state.cli_flag}`,
|
|
84
|
+
];
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
id: state.key,
|
|
88
|
+
label: state.label,
|
|
89
|
+
description: detail_lines.join('\n'),
|
|
90
|
+
currentValue: state.saved_enabled ? ENABLED : DISABLED,
|
|
91
|
+
values: [ENABLED, DISABLED],
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function sets_equal(
|
|
96
|
+
a: ReadonlySet<string>,
|
|
97
|
+
b: ReadonlySet<string>,
|
|
98
|
+
): boolean {
|
|
99
|
+
if (a.size !== b.size) return false;
|
|
100
|
+
for (const value of a) {
|
|
101
|
+
if (!b.has(value)) return false;
|
|
102
|
+
}
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function search_states(
|
|
107
|
+
states: BuiltinExtensionState[],
|
|
108
|
+
query: string,
|
|
109
|
+
): BuiltinExtensionState[] {
|
|
110
|
+
const normalized = query.trim().toLowerCase();
|
|
111
|
+
if (!normalized) return states;
|
|
112
|
+
|
|
113
|
+
return states.filter((state) =>
|
|
114
|
+
[
|
|
115
|
+
state.key,
|
|
116
|
+
state.label,
|
|
117
|
+
state.description,
|
|
118
|
+
...state.aliases,
|
|
119
|
+
].some((value) => value.toLowerCase().includes(normalized)),
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
function save_extension_enabled(
|
|
124
|
+
key: BuiltinExtensionKey,
|
|
125
|
+
enabled: boolean,
|
|
126
|
+
): void {
|
|
127
|
+
const config = load_builtin_extensions_config();
|
|
128
|
+
config.enabled[key] = enabled;
|
|
129
|
+
save_builtin_extensions_config(config);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
export function create_extensions_extension(
|
|
133
|
+
options: ExtensionsManagerOptions = {},
|
|
134
|
+
) {
|
|
135
|
+
const force_disabled = to_force_disabled_set(
|
|
136
|
+
options.force_disabled,
|
|
137
|
+
);
|
|
138
|
+
|
|
139
|
+
return async function extensions(pi: ExtensionAPI) {
|
|
140
|
+
const subs = ['list', 'enable', 'disable', 'toggle', 'search'];
|
|
141
|
+
|
|
142
|
+
pi.registerCommand('extensions', {
|
|
143
|
+
description: 'Manage built-in my-pi extensions',
|
|
144
|
+
getArgumentCompletions: (prefix) => {
|
|
145
|
+
const parts = prefix.trim().split(/\s+/);
|
|
146
|
+
if (parts.length <= 1) {
|
|
147
|
+
return subs
|
|
148
|
+
.filter((sub) => sub.startsWith(parts[0] || ''))
|
|
149
|
+
.map((sub) => ({ value: sub, label: sub }));
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
if (['enable', 'disable', 'toggle'].includes(parts[0])) {
|
|
153
|
+
const q = parts.slice(1).join(' ').toLowerCase();
|
|
154
|
+
return resolve_builtin_extension_states(force_disabled)
|
|
155
|
+
.filter(
|
|
156
|
+
(state) =>
|
|
157
|
+
state.key.toLowerCase().includes(q) ||
|
|
158
|
+
state.label.toLowerCase().includes(q),
|
|
159
|
+
)
|
|
160
|
+
.slice(0, 20)
|
|
161
|
+
.map((state) => ({
|
|
162
|
+
value: `${parts[0]} ${state.key}`,
|
|
163
|
+
label: `${state.key} ${state.saved_enabled ? ENABLED : DISABLED}`,
|
|
164
|
+
}));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return null;
|
|
168
|
+
},
|
|
169
|
+
handler: async (args, ctx) => {
|
|
170
|
+
const trimmed = args.trim();
|
|
171
|
+
|
|
172
|
+
if (!trimmed && ctx.hasUI) {
|
|
173
|
+
const states =
|
|
174
|
+
resolve_builtin_extension_states(force_disabled);
|
|
175
|
+
const initial_enabled = new Set(
|
|
176
|
+
states
|
|
177
|
+
.filter((state) => state.saved_enabled)
|
|
178
|
+
.map((state) => state.key),
|
|
179
|
+
);
|
|
180
|
+
const current_enabled = new Set(initial_enabled);
|
|
181
|
+
|
|
182
|
+
await ctx.ui.custom((tui, theme, _kb, done) => {
|
|
183
|
+
const items = states.map(to_setting_item);
|
|
184
|
+
const container = new Container();
|
|
185
|
+
|
|
186
|
+
container.addChild({
|
|
187
|
+
render: () => {
|
|
188
|
+
const saved_enabled = current_enabled.size;
|
|
189
|
+
const saved_disabled = states.length - saved_enabled;
|
|
190
|
+
const enabled_now = [...current_enabled].filter(
|
|
191
|
+
(key) =>
|
|
192
|
+
!force_disabled.has(key as BuiltinExtensionKey),
|
|
193
|
+
).length;
|
|
194
|
+
const disabled_now = states.length - enabled_now;
|
|
195
|
+
return [
|
|
196
|
+
theme.fg(
|
|
197
|
+
'accent',
|
|
198
|
+
theme.bold('Built-in extensions'),
|
|
199
|
+
),
|
|
200
|
+
theme.fg(
|
|
201
|
+
'muted',
|
|
202
|
+
`${saved_enabled} saved enabled • ${saved_disabled} saved disabled • ${enabled_now} enabled now • ${disabled_now} disabled now`,
|
|
203
|
+
),
|
|
204
|
+
'',
|
|
205
|
+
];
|
|
206
|
+
},
|
|
207
|
+
invalidate: () => {},
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
const settings_list = new SettingsList(
|
|
211
|
+
items,
|
|
212
|
+
Math.min(Math.max(items.length + 4, 8), 16),
|
|
213
|
+
{
|
|
214
|
+
cursor: theme.fg('accent', '›'),
|
|
215
|
+
label: (text, selected) =>
|
|
216
|
+
selected ? theme.fg('accent', text) : text,
|
|
217
|
+
value: (text, selected) => {
|
|
218
|
+
const color = text === ENABLED ? 'success' : 'dim';
|
|
219
|
+
const rendered = theme.fg(color, text);
|
|
220
|
+
return selected
|
|
221
|
+
? theme.bold(theme.fg('accent', rendered))
|
|
222
|
+
: rendered;
|
|
223
|
+
},
|
|
224
|
+
description: (text) => theme.fg('muted', text),
|
|
225
|
+
hint: (text) => theme.fg('dim', text),
|
|
226
|
+
},
|
|
227
|
+
(id, new_value) => {
|
|
228
|
+
const key = id as BuiltinExtensionKey;
|
|
229
|
+
const enabled = new_value === ENABLED;
|
|
230
|
+
if (enabled) {
|
|
231
|
+
current_enabled.add(key);
|
|
232
|
+
} else {
|
|
233
|
+
current_enabled.delete(key);
|
|
234
|
+
}
|
|
235
|
+
save_extension_enabled(key, enabled);
|
|
236
|
+
},
|
|
237
|
+
() => done(undefined),
|
|
238
|
+
{ enableSearch: true },
|
|
239
|
+
);
|
|
240
|
+
|
|
241
|
+
container.addChild(settings_list);
|
|
242
|
+
container.addChild(
|
|
243
|
+
new Text(
|
|
244
|
+
theme.fg(
|
|
245
|
+
'dim',
|
|
246
|
+
'esc close • search filters • changes save immediately • CLI --no-* flags still win in this process',
|
|
247
|
+
),
|
|
248
|
+
0,
|
|
249
|
+
1,
|
|
250
|
+
),
|
|
251
|
+
);
|
|
252
|
+
|
|
253
|
+
return {
|
|
254
|
+
render(width: number) {
|
|
255
|
+
return container.render(width);
|
|
256
|
+
},
|
|
257
|
+
invalidate() {
|
|
258
|
+
container.invalidate();
|
|
259
|
+
},
|
|
260
|
+
handleInput(data: string) {
|
|
261
|
+
settings_list.handleInput(data);
|
|
262
|
+
tui.requestRender();
|
|
263
|
+
},
|
|
264
|
+
};
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
if (!sets_equal(initial_enabled, current_enabled)) {
|
|
268
|
+
ctx.ui.notify(
|
|
269
|
+
force_disabled.size > 0
|
|
270
|
+
? 'Reloading to apply updated built-in extensions. CLI --no-* flags still force-disable some extensions in this process.'
|
|
271
|
+
: 'Reloading to apply updated built-in extensions...',
|
|
272
|
+
'info',
|
|
273
|
+
);
|
|
274
|
+
await ctx.reload();
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
const [sub, ...rest] = (trimmed || 'list').split(/\s+/);
|
|
282
|
+
const arg = rest.join(' ');
|
|
283
|
+
const states =
|
|
284
|
+
resolve_builtin_extension_states(force_disabled);
|
|
285
|
+
|
|
286
|
+
switch (sub) {
|
|
287
|
+
case 'list': {
|
|
288
|
+
ctx.ui.notify(
|
|
289
|
+
format_extension_lines(states, {
|
|
290
|
+
heading: 'Built-in extensions',
|
|
291
|
+
}),
|
|
292
|
+
);
|
|
293
|
+
break;
|
|
294
|
+
}
|
|
295
|
+
case 'enable':
|
|
296
|
+
case 'disable':
|
|
297
|
+
case 'toggle': {
|
|
298
|
+
if (!arg) {
|
|
299
|
+
ctx.ui.notify(
|
|
300
|
+
`Usage: /extensions ${sub} <key>`,
|
|
301
|
+
'warning',
|
|
302
|
+
);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const extension = find_builtin_extension(arg);
|
|
307
|
+
if (!extension) {
|
|
308
|
+
ctx.ui.notify(
|
|
309
|
+
`Unknown extension: ${arg}. Use: ${BUILTIN_EXTENSIONS.map((item) => item.key).join(', ')}`,
|
|
310
|
+
'warning',
|
|
311
|
+
);
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const current_state = states.find(
|
|
316
|
+
(state) => state.key === extension.key,
|
|
317
|
+
);
|
|
318
|
+
const next_enabled =
|
|
319
|
+
sub === 'enable'
|
|
320
|
+
? true
|
|
321
|
+
: sub === 'disable'
|
|
322
|
+
? false
|
|
323
|
+
: !current_state?.saved_enabled;
|
|
324
|
+
save_extension_enabled(extension.key, next_enabled);
|
|
325
|
+
|
|
326
|
+
ctx.ui.notify(
|
|
327
|
+
next_enabled && force_disabled.has(extension.key)
|
|
328
|
+
? `Enabled ${extension.key} in saved config. Still disabled in this process by ${extension.cli_flag}. /reload or restart without that flag to apply.`
|
|
329
|
+
: `${extension.key} ${next_enabled ? 'enabled' : 'disabled'}. /reload to apply.`,
|
|
330
|
+
);
|
|
331
|
+
break;
|
|
332
|
+
}
|
|
333
|
+
case 'search': {
|
|
334
|
+
if (!arg) {
|
|
335
|
+
ctx.ui.notify(
|
|
336
|
+
'Usage: /extensions search <query>',
|
|
337
|
+
'warning',
|
|
338
|
+
);
|
|
339
|
+
return;
|
|
340
|
+
}
|
|
341
|
+
const results = search_states(states, arg);
|
|
342
|
+
if (results.length === 0) {
|
|
343
|
+
ctx.ui.notify(
|
|
344
|
+
`No built-in extensions matching "${arg}"`,
|
|
345
|
+
);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
ctx.ui.notify(
|
|
349
|
+
format_extension_lines(results, {
|
|
350
|
+
heading: `Built-in extensions matching "${arg}"`,
|
|
351
|
+
}),
|
|
352
|
+
);
|
|
353
|
+
break;
|
|
354
|
+
}
|
|
355
|
+
default:
|
|
356
|
+
ctx.ui.notify(
|
|
357
|
+
`Unknown: ${sub}. Use: ${subs.join(', ')}`,
|
|
358
|
+
'warning',
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
},
|
|
362
|
+
});
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
export default create_extensions_extension();
|