@homer0/pi-profiles 0.0.0-stage → 1.0.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/README.md +200 -2
- package/dist/extensions/profiles.js +2 -0
- package/dist/extensions/profiles.js.map +1 -0
- package/dist/handlers-CDMu6gs8.js +4 -0
- package/dist/handlers-CDMu6gs8.js.map +1 -0
- package/dist/index.js +1 -0
- package/dist/utils-IUrXjb0h.js +2 -0
- package/dist/utils-IUrXjb0h.js.map +1 -0
- package/package.json +70 -4
- package/src/extensions/profiles.ts +62 -0
- package/src/handlers.ts +181 -0
- package/src/index.ts +3 -0
- package/src/resources.ts +323 -0
- package/src/schemas.ts +44 -0
- package/src/types.ts +97 -0
- package/src/utils.ts +253 -0
package/src/index.ts
ADDED
package/src/resources.ts
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import {
|
|
2
|
+
DefaultPackageManager,
|
|
3
|
+
SettingsManager,
|
|
4
|
+
getAgentDir,
|
|
5
|
+
type ExtensionAPI,
|
|
6
|
+
type ExtensionContext,
|
|
7
|
+
type ExtensionHandler,
|
|
8
|
+
type SessionStartEvent,
|
|
9
|
+
} from '@earendil-works/pi-coding-agent';
|
|
10
|
+
import { existsSync, statSync } from 'node:fs';
|
|
11
|
+
import { readFile } from 'node:fs/promises';
|
|
12
|
+
import { dirname, isAbsolute, join } from 'node:path';
|
|
13
|
+
import { profilePackagesSchema, type PackageEntry } from './schemas.js';
|
|
14
|
+
import type {
|
|
15
|
+
LoadProfileExtensionsOptions,
|
|
16
|
+
PackageResourcePaths,
|
|
17
|
+
ProfileLibOptions,
|
|
18
|
+
ProfileResources,
|
|
19
|
+
ResolveProfilePackageResourcesOptions,
|
|
20
|
+
} from './types.js';
|
|
21
|
+
import {
|
|
22
|
+
ensureErrorMessage,
|
|
23
|
+
getAgentPath,
|
|
24
|
+
resolveEnabledProfiles,
|
|
25
|
+
resolveScriptPath,
|
|
26
|
+
runScript,
|
|
27
|
+
warnProfilesIssue,
|
|
28
|
+
} from './utils.js';
|
|
29
|
+
|
|
30
|
+
const AGENT_DIR = getAgentDir();
|
|
31
|
+
const PROFILES_DIR = getAgentPath('profiles');
|
|
32
|
+
const PACKAGES_FILENAME = 'packages.json';
|
|
33
|
+
const AGENTS_FILENAME = 'AGENTS.md';
|
|
34
|
+
const SKILLS_DIRNAME = 'skills';
|
|
35
|
+
|
|
36
|
+
/** Factory function signature exported by a pi extension module. */
|
|
37
|
+
type ExtensionFactory = (pi: ExtensionAPI) => void | Promise<void>;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Checks if a path is an existing directory.
|
|
41
|
+
*
|
|
42
|
+
* @param path The path to check.
|
|
43
|
+
* @returns Whether it's a directory.
|
|
44
|
+
*/
|
|
45
|
+
const isDirectory = (path: string): boolean => {
|
|
46
|
+
try {
|
|
47
|
+
return statSync(path).isDirectory();
|
|
48
|
+
} catch {
|
|
49
|
+
return false;
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Resolves a single package entry to a path, evaluating its `enabledIf` condition if it
|
|
55
|
+
* has one.
|
|
56
|
+
*
|
|
57
|
+
* @param entry The entry from the profile's `packages.json`.
|
|
58
|
+
* @param profileDir The directory of the profile, to resolve the script against.
|
|
59
|
+
* @returns The path of the package, or `undefined` if it's turned off.
|
|
60
|
+
*/
|
|
61
|
+
const resolvePackageEntry = async (
|
|
62
|
+
entry: PackageEntry,
|
|
63
|
+
profileDir: string,
|
|
64
|
+
): Promise<string | undefined> => {
|
|
65
|
+
if (typeof entry === 'string') return entry;
|
|
66
|
+
const { path: pkgPath, enabledIf } = entry;
|
|
67
|
+
if (enabledIf === false || enabledIf === 'false') return undefined;
|
|
68
|
+
const resolved = resolveScriptPath(enabledIf, profileDir);
|
|
69
|
+
if (typeof resolved === 'undefined') {
|
|
70
|
+
warnProfilesIssue(
|
|
71
|
+
`Could not resolve package enabledIf script for "${pkgPath}": ${enabledIf}`,
|
|
72
|
+
);
|
|
73
|
+
return undefined;
|
|
74
|
+
}
|
|
75
|
+
return (await runScript(resolved)) ? pkgPath : undefined;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Reads the packages a profile declares, evaluating the `enabledIf` condition of each. If
|
|
80
|
+
* the file is invalid, it reports it through `onError` and the profile has no packages.
|
|
81
|
+
*
|
|
82
|
+
* @param packagesPath The path of the profile's `packages.json`.
|
|
83
|
+
* @param onError Where to report non-fatal issues.
|
|
84
|
+
* @returns The paths of the packages, as declared.
|
|
85
|
+
*/
|
|
86
|
+
const readProfilePackages = async (
|
|
87
|
+
packagesPath: string,
|
|
88
|
+
onError: (message: string) => void,
|
|
89
|
+
): Promise<string[]> => {
|
|
90
|
+
if (!existsSync(packagesPath)) return [];
|
|
91
|
+
const profileDir = dirname(packagesPath);
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
const parsed = JSON.parse(await readFile(packagesPath, 'utf8')) as unknown;
|
|
95
|
+
const { packages } = profilePackagesSchema.parse(parsed);
|
|
96
|
+
const results: string[] = [];
|
|
97
|
+
for (const entry of packages) {
|
|
98
|
+
const pkgPath = await resolvePackageEntry(entry, profileDir);
|
|
99
|
+
if (typeof pkgPath !== 'undefined') results.push(pkgPath);
|
|
100
|
+
}
|
|
101
|
+
return results;
|
|
102
|
+
} catch (error) {
|
|
103
|
+
onError(
|
|
104
|
+
`Invalid profile packages file ${packagesPath}; skipped. ${ensureErrorMessage(error)}`,
|
|
105
|
+
);
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Discovers what every enabled profile exposes: its `skills` directory, its `AGENTS.md`
|
|
112
|
+
* context file, and its declared packages. Whatever can't be read is reported through
|
|
113
|
+
* `onError` and skipped.
|
|
114
|
+
*
|
|
115
|
+
* @param options The directory to resolve from, and where to report non-fatal issues.
|
|
116
|
+
* @returns The names of the profiles and what they expose.
|
|
117
|
+
*/
|
|
118
|
+
export const discoverProfileResources = async (
|
|
119
|
+
options: ProfileLibOptions = {},
|
|
120
|
+
): Promise<ProfileResources> => {
|
|
121
|
+
const onError = options.onError ?? ((message: string) => warnProfilesIssue(message));
|
|
122
|
+
const names = await resolveEnabledProfiles(options);
|
|
123
|
+
const resources: ProfileResources = {
|
|
124
|
+
names,
|
|
125
|
+
skillPaths: [],
|
|
126
|
+
agentsFiles: [],
|
|
127
|
+
packages: [],
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
if (names.length === 0) return resources;
|
|
131
|
+
|
|
132
|
+
for (const name of names) {
|
|
133
|
+
const profileDir = join(PROFILES_DIR, name);
|
|
134
|
+
const skillsPath = join(profileDir, SKILLS_DIRNAME);
|
|
135
|
+
const agentsPath = join(profileDir, AGENTS_FILENAME);
|
|
136
|
+
const packagesPath = join(profileDir, PACKAGES_FILENAME);
|
|
137
|
+
|
|
138
|
+
if (isDirectory(skillsPath)) resources.skillPaths.push(skillsPath);
|
|
139
|
+
|
|
140
|
+
if (existsSync(agentsPath)) {
|
|
141
|
+
try {
|
|
142
|
+
resources.agentsFiles.push({
|
|
143
|
+
path: agentsPath,
|
|
144
|
+
content: await readFile(agentsPath, 'utf8'),
|
|
145
|
+
});
|
|
146
|
+
} catch (error) {
|
|
147
|
+
onError(
|
|
148
|
+
`Could not read profile context ${agentsPath}; skipped. ${ensureErrorMessage(error)}`,
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const profilePackages = await readProfilePackages(packagesPath, onError);
|
|
154
|
+
resources.packages.push(
|
|
155
|
+
...profilePackages.map((pkg) => (isAbsolute(pkg) ? pkg : join(profileDir, pkg))),
|
|
156
|
+
);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
return resources;
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* One queued `session_start` handler registration, captured by {@link wrapPiForReplay} so
|
|
164
|
+
* it can be replayed after its factory returns.
|
|
165
|
+
*/
|
|
166
|
+
type QueuedSessionStartReplay = () => Promise<void>;
|
|
167
|
+
|
|
168
|
+
/** Options for {@link wrapPiForReplay}. */
|
|
169
|
+
type WrapPiForReplayOptions = {
|
|
170
|
+
/** The Pi extension API to wrap. */
|
|
171
|
+
pi: ExtensionAPI;
|
|
172
|
+
/** The in-flight `session_start` event. */
|
|
173
|
+
sessionStartEvent: SessionStartEvent;
|
|
174
|
+
/** The context of the in-flight `session_start`. */
|
|
175
|
+
ctx: ExtensionContext;
|
|
176
|
+
};
|
|
177
|
+
|
|
178
|
+
/** Return value of {@link wrapPiForReplay}: the wrapped `pi` plus its replay queue. */
|
|
179
|
+
type WrapPiForReplayResult = { pi: ExtensionAPI; queue: QueuedSessionStartReplay[] };
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* Wraps `pi` for one dynamically-loaded factory call so any `pi.on('session_start', ...)`
|
|
183
|
+
* it makes still registers normally, but is also queued for replay. Pi snapshots
|
|
184
|
+
* registered handlers before dispatching an event, so a handler registered by a factory
|
|
185
|
+
* invoked from inside an in-flight `session_start` handler would otherwise never run for
|
|
186
|
+
* the event that triggered this load.
|
|
187
|
+
*
|
|
188
|
+
* @param options The API to wrap, and the event and context to replay.
|
|
189
|
+
* @returns The wrapped `pi`, and the replay queue in registration order.
|
|
190
|
+
*/
|
|
191
|
+
const wrapPiForReplay = ({
|
|
192
|
+
pi,
|
|
193
|
+
sessionStartEvent,
|
|
194
|
+
ctx,
|
|
195
|
+
}: WrapPiForReplayOptions): WrapPiForReplayResult => {
|
|
196
|
+
const queue: QueuedSessionStartReplay[] = [];
|
|
197
|
+
// `ExtensionAPI.on` is a large overload union; a single forwarding implementation can't
|
|
198
|
+
// match every overload, so the event/handler pair is cast through `never` at the
|
|
199
|
+
// forwarding call and the finished function is cast once to the real overloaded type.
|
|
200
|
+
const on = ((eventType: string, handler: ExtensionHandler<SessionStartEvent>) => {
|
|
201
|
+
const unsubscribe = pi.on(eventType as never, handler as never);
|
|
202
|
+
if (eventType === 'session_start') {
|
|
203
|
+
queue.push(async () => {
|
|
204
|
+
await handler(sessionStartEvent, ctx);
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
return unsubscribe;
|
|
208
|
+
}) as ExtensionAPI['on'];
|
|
209
|
+
|
|
210
|
+
return { pi: { ...pi, on }, queue };
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Imports and runs the extension factories of the profile packages. It's meant to be
|
|
215
|
+
* called from a `session_start` handler: the `session_start` handlers each factory
|
|
216
|
+
* registers are replayed right after, with the same event, because Pi's own dispatch for
|
|
217
|
+
* it has already taken its snapshot of the handlers. A handler that fails is reported and
|
|
218
|
+
* doesn't stop the others.
|
|
219
|
+
*
|
|
220
|
+
* @param pi The Pi extension API to give to the factories.
|
|
221
|
+
* @param options The packages to load, and the event and context to replay.
|
|
222
|
+
*/
|
|
223
|
+
export const loadProfileExtensions = async (
|
|
224
|
+
pi: ExtensionAPI,
|
|
225
|
+
options: LoadProfileExtensionsOptions,
|
|
226
|
+
): Promise<void> => {
|
|
227
|
+
const { packages, sessionStartEvent, ctx } = options;
|
|
228
|
+
if (packages.length === 0) return;
|
|
229
|
+
const cwd = options.cwd ?? process.cwd();
|
|
230
|
+
const onError = options.onError ?? ((message: string) => warnProfilesIssue(message));
|
|
231
|
+
|
|
232
|
+
try {
|
|
233
|
+
// Resolve the pi.extensions entry points declared by each profile package.
|
|
234
|
+
const settingsManager = SettingsManager.create(cwd, AGENT_DIR);
|
|
235
|
+
const packageManager = new DefaultPackageManager({
|
|
236
|
+
cwd,
|
|
237
|
+
agentDir: AGENT_DIR,
|
|
238
|
+
settingsManager,
|
|
239
|
+
});
|
|
240
|
+
const resolved = await packageManager.resolveExtensionSources(packages, {
|
|
241
|
+
temporary: true,
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
// Import each extension file and call its factory with a replay-wrapped pi API, then
|
|
245
|
+
// replay any session_start registrations it made, in registration order, with
|
|
246
|
+
// per-handler error isolation matching pi's own emit() behavior.
|
|
247
|
+
for (const ext of resolved.extensions) {
|
|
248
|
+
// Guard against the directory-path fallback that resolveExtensionSources emits
|
|
249
|
+
// when a package has no pi.extensions manifest and no index.ts/index.js.
|
|
250
|
+
if (ext.enabled && (ext.path.endsWith('.ts') || ext.path.endsWith('.js'))) {
|
|
251
|
+
try {
|
|
252
|
+
const imported = await import(ext.path);
|
|
253
|
+
const factory = imported?.default;
|
|
254
|
+
if (typeof factory === 'function') {
|
|
255
|
+
const { pi: replayPi, queue } = wrapPiForReplay({
|
|
256
|
+
pi,
|
|
257
|
+
sessionStartEvent,
|
|
258
|
+
ctx,
|
|
259
|
+
});
|
|
260
|
+
await (factory as ExtensionFactory)(replayPi);
|
|
261
|
+
for (const replay of queue) {
|
|
262
|
+
try {
|
|
263
|
+
await replay();
|
|
264
|
+
} catch (error) {
|
|
265
|
+
onError(
|
|
266
|
+
`Profile extension ${ext.path} session_start handler failed. ${ensureErrorMessage(error)}`,
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
} catch (error) {
|
|
272
|
+
onError(
|
|
273
|
+
`Could not load profile extension ${ext.path}; skipped. ${ensureErrorMessage(error)}`,
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
} catch (error) {
|
|
279
|
+
onError(`Could not load profile extensions; skipped. ${ensureErrorMessage(error)}`);
|
|
280
|
+
}
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Resolves the skills, prompt templates and themes the profile packages expose. If they
|
|
285
|
+
* can't be resolved, it reports it through `onError` and there are none.
|
|
286
|
+
*
|
|
287
|
+
* @param options The packages to resolve, the directory to resolve from, and where to
|
|
288
|
+
* report non-fatal issues.
|
|
289
|
+
* @returns The paths of the enabled resources, by kind.
|
|
290
|
+
*/
|
|
291
|
+
export const resolveProfilePackageResources = async (
|
|
292
|
+
options: ResolveProfilePackageResourcesOptions,
|
|
293
|
+
): Promise<PackageResourcePaths> => {
|
|
294
|
+
const { packages } = options;
|
|
295
|
+
if (packages.length === 0) {
|
|
296
|
+
return { skillPaths: [], promptPaths: [], themePaths: [] };
|
|
297
|
+
}
|
|
298
|
+
const cwd = options.cwd ?? process.cwd();
|
|
299
|
+
const onError = options.onError ?? ((message: string) => warnProfilesIssue(message));
|
|
300
|
+
|
|
301
|
+
try {
|
|
302
|
+
const settingsManager = SettingsManager.create(cwd, AGENT_DIR);
|
|
303
|
+
const packageManager = new DefaultPackageManager({
|
|
304
|
+
cwd,
|
|
305
|
+
agentDir: AGENT_DIR,
|
|
306
|
+
settingsManager,
|
|
307
|
+
});
|
|
308
|
+
const resolved = await packageManager.resolveExtensionSources(packages, {
|
|
309
|
+
temporary: true,
|
|
310
|
+
});
|
|
311
|
+
const enabledPaths = (resources: typeof resolved.skills): string[] =>
|
|
312
|
+
resources.filter((resource) => resource.enabled).map((resource) => resource.path);
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
skillPaths: enabledPaths(resolved.skills),
|
|
316
|
+
promptPaths: enabledPaths(resolved.prompts),
|
|
317
|
+
themePaths: enabledPaths(resolved.themes),
|
|
318
|
+
};
|
|
319
|
+
} catch (error) {
|
|
320
|
+
onError(`Could not load profile packages; skipped. ${ensureErrorMessage(error)}`);
|
|
321
|
+
return { skillPaths: [], promptPaths: [], themePaths: [] };
|
|
322
|
+
}
|
|
323
|
+
};
|
package/src/schemas.ts
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
/** Runtime schema for `pi.profiles` in the Pi config. */
|
|
4
|
+
export const profilesConfigSchema = z.object({
|
|
5
|
+
/** The names of the profiles that are always enabled. */
|
|
6
|
+
enabled: z.array(z.string().min(1)).default([]),
|
|
7
|
+
/**
|
|
8
|
+
* The conditional profiles: each key is a profile name, and each value is the path of a
|
|
9
|
+
* script that prints `true` when the profile should be enabled, or `false` to turn the
|
|
10
|
+
* entry off.
|
|
11
|
+
*/
|
|
12
|
+
enabledIf: z
|
|
13
|
+
.record(z.string().min(1), z.union([z.literal(false), z.string().min(1)]))
|
|
14
|
+
.default({}),
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
/** The resolved `pi.profiles` config. */
|
|
18
|
+
export type ProfilesConfig = z.infer<typeof profilesConfigSchema>;
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Runtime schema for one entry in a profile's `packages.json`: a path, or an object with
|
|
22
|
+
* the path and a condition.
|
|
23
|
+
*/
|
|
24
|
+
export const packageEntrySchema = z.union([
|
|
25
|
+
z.string().min(1),
|
|
26
|
+
z.object({
|
|
27
|
+
/** The path of the package, absolute or relative to the profile directory. */
|
|
28
|
+
path: z.string().min(1),
|
|
29
|
+
/**
|
|
30
|
+
* The path of a script that prints `true` when the package should be loaded, or
|
|
31
|
+
* `false` to turn the entry off.
|
|
32
|
+
*/
|
|
33
|
+
enabledIf: z.union([z.literal(false), z.string().min(1)]),
|
|
34
|
+
}),
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
/** One entry in a profile's `packages.json`. */
|
|
38
|
+
export type PackageEntry = z.infer<typeof packageEntrySchema>;
|
|
39
|
+
|
|
40
|
+
/** Runtime schema for a profile's `packages.json` file. */
|
|
41
|
+
export const profilePackagesSchema = z.object({
|
|
42
|
+
/** The packages the profile loads. */
|
|
43
|
+
packages: z.array(packageEntrySchema).default([]),
|
|
44
|
+
});
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
ExtensionAPI,
|
|
3
|
+
ExtensionContext,
|
|
4
|
+
SessionStartEvent,
|
|
5
|
+
} from '@earendil-works/pi-coding-agent';
|
|
6
|
+
import type { ProfilesConfig } from './schemas.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Options shared by every profile-resolution/discovery function in this lib. Callers with
|
|
10
|
+
* an active Pi session (e.g. the `profiles` extension) pass their own `onError` to
|
|
11
|
+
* surface non-fatal issues through the session UI; other callers fall back to a
|
|
12
|
+
* `console.warn`-based reporter.
|
|
13
|
+
*/
|
|
14
|
+
export type ProfileLibOptions = {
|
|
15
|
+
/**
|
|
16
|
+
* Working directory used to resolve config files and `enabledIf` scripts.
|
|
17
|
+
*
|
|
18
|
+
* @default process.cwd()
|
|
19
|
+
*/
|
|
20
|
+
cwd?: string;
|
|
21
|
+
/** Called with a human-readable message when resolution hits a non-fatal issue. */
|
|
22
|
+
onError?: (message: string) => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
/** Resources discovered across every currently-enabled profile. */
|
|
26
|
+
export type ProfileResources = {
|
|
27
|
+
/** The names of the enabled profiles. */
|
|
28
|
+
names: string[];
|
|
29
|
+
/** The `skills` directories of the enabled profiles that have one. */
|
|
30
|
+
skillPaths: string[];
|
|
31
|
+
/** The `AGENTS.md` files of the enabled profiles, with their content. */
|
|
32
|
+
agentsFiles: Array<{ path: string; content: string }>;
|
|
33
|
+
/** The absolute paths of the packages the enabled profiles declare. */
|
|
34
|
+
packages: string[];
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
/** Resource paths exposed by a profile's declared packages. */
|
|
38
|
+
export type PackageResourcePaths = {
|
|
39
|
+
/** The paths of the skills. */
|
|
40
|
+
skillPaths: string[];
|
|
41
|
+
/** The paths of the prompt templates. */
|
|
42
|
+
promptPaths: string[];
|
|
43
|
+
/** The paths of the themes. */
|
|
44
|
+
themePaths: string[];
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/** Options for {@link loadProfileExtensions}. */
|
|
48
|
+
export type LoadProfileExtensionsOptions = ProfileLibOptions & {
|
|
49
|
+
/**
|
|
50
|
+
* Resolved package paths (from {@link ProfileResources.packages}) to load extensions
|
|
51
|
+
* from.
|
|
52
|
+
*/
|
|
53
|
+
packages: string[];
|
|
54
|
+
/**
|
|
55
|
+
* The in-flight `session_start` event, replayed for each dynamically loaded extension's
|
|
56
|
+
* own `session_start` handler(s). Required because pi snapshots registered handlers
|
|
57
|
+
* before dispatching an event, so a handler registered here (after that snapshot, since
|
|
58
|
+
* this function itself runs from inside a `session_start` handler) would otherwise
|
|
59
|
+
* never run for the very event that triggered this load.
|
|
60
|
+
*/
|
|
61
|
+
sessionStartEvent: SessionStartEvent;
|
|
62
|
+
/** Extension context for the in-flight `session_start`, passed to replayed handlers. */
|
|
63
|
+
ctx: ExtensionContext;
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
/** Options for {@link resolveProfilePackageResources}. */
|
|
67
|
+
export type ResolveProfilePackageResourcesOptions = ProfileLibOptions & {
|
|
68
|
+
/**
|
|
69
|
+
* Resolved package paths (from {@link ProfileResources.packages}) to resolve resources
|
|
70
|
+
* from.
|
|
71
|
+
*/
|
|
72
|
+
packages: string[];
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
/** Mutable state shared by the entry and the lazily loaded handlers. */
|
|
76
|
+
export type ProfilesState = {
|
|
77
|
+
/** Names of the profiles discovered for the current session. */
|
|
78
|
+
sessionProfiles: string[];
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* What the lazy handlers receive from the entry.
|
|
83
|
+
*
|
|
84
|
+
* This module is imported eagerly at startup: it must not import anything heavy.
|
|
85
|
+
*/
|
|
86
|
+
export type ProfilesRuntime = {
|
|
87
|
+
/** The Pi extension API. */
|
|
88
|
+
pi: ExtensionAPI;
|
|
89
|
+
/** The state shared by the handlers. */
|
|
90
|
+
state: ProfilesState;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
declare module '@homer0/pi-config' {
|
|
94
|
+
interface PiConfig {
|
|
95
|
+
profiles?: ProfilesConfig;
|
|
96
|
+
}
|
|
97
|
+
}
|