pi-profile-switch 0.1.0 → 0.3.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 +10 -36
- package/README.zh-CN.md +10 -36
- package/examples/profiles.example.json +74 -0
- package/examples/profiles.json +6 -28
- package/extensions/pi-profile-switch/index.ts +213 -18
- package/package.json +1 -1
- package/schemas/profiles.schema.json +2 -8
- package/src/adapter-presence.ts +75 -0
- package/src/json-file.ts +20 -0
- package/src/mcp-config.ts +153 -34
- package/src/mcp-overlay-file.ts +35 -0
- package/src/mcp-overlay.ts +122 -0
- package/src/profile-badge.ts +142 -0
- package/src/profile-catalog-store.ts +4 -5
- package/src/profile-catalog.ts +17 -43
- package/src/profile-presets.ts +61 -0
- package/src/profile-resolver.ts +51 -18
- package/src/runtime-state-store.ts +13 -0
- package/src/startup-mcp-scope.ts +262 -0
- package/src/startup-selection.ts +1 -4
- package/src/switching/activate-profile.ts +8 -6
- package/src/switching/list-profiles.ts +8 -11
- package/src/switching/profile-wizard.ts +39 -6
|
@@ -24,20 +24,17 @@ export async function listProfiles(input: {
|
|
|
24
24
|
realAgentDir: string;
|
|
25
25
|
cwd: string;
|
|
26
26
|
projectTrusted: boolean;
|
|
27
|
-
}): Promise<
|
|
27
|
+
}): Promise<ProfileListEntry[]> {
|
|
28
28
|
const catalog = await ProfileCatalog.load(input.realAgentDir, {
|
|
29
29
|
projectDir: input.projectTrusted ? input.cwd : undefined,
|
|
30
30
|
});
|
|
31
|
-
return {
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
})),
|
|
39
|
-
warnings: [...catalog.warnings],
|
|
40
|
-
};
|
|
31
|
+
return catalog.list().map((profile) => ({
|
|
32
|
+
name: profile.name,
|
|
33
|
+
source: profile.source,
|
|
34
|
+
...(typeof profile.definition.label === "string" ? { label: profile.definition.label } : {}),
|
|
35
|
+
...(typeof profile.definition.description === "string" ? { description: profile.definition.description } : {}),
|
|
36
|
+
shadowsGlobal: profile.source === "project" && catalog.shadowsGlobal(profile.name),
|
|
37
|
+
}));
|
|
41
38
|
}
|
|
42
39
|
|
|
43
40
|
export function formatProfileList(entries: ProfileListEntry[], activeProfile?: string): string {
|
|
@@ -8,9 +8,14 @@
|
|
|
8
8
|
* keeps the current value (prefill via placeholder); there is no
|
|
9
9
|
* field-clearing gesture (delete + create instead). Any cancelled step
|
|
10
10
|
* aborts the wizard — nothing is written.
|
|
11
|
+
*
|
|
12
|
+
* Create can start from a shipped preset (src/profile-presets.ts). A preset is
|
|
13
|
+
* prefilled form state only: the wizard copies its complete definition into the
|
|
14
|
+
* chosen catalog, and the new profile belongs to the user from then on.
|
|
11
15
|
*/
|
|
12
16
|
|
|
13
17
|
import type { ProfileDefinition } from "../profile-catalog.ts";
|
|
18
|
+
import { PROFILE_PRESETS, type ProfilePreset } from "../profile-presets.ts";
|
|
14
19
|
import type { CatalogScope } from "./profile-crud.ts";
|
|
15
20
|
|
|
16
21
|
export interface ProfileWizardUi {
|
|
@@ -22,6 +27,8 @@ export interface ProfileWizardResult {
|
|
|
22
27
|
scope: CatalogScope;
|
|
23
28
|
name: string;
|
|
24
29
|
definition: ProfileDefinition;
|
|
30
|
+
/** Name of the preset this definition was copied from, when any. */
|
|
31
|
+
preset?: string;
|
|
25
32
|
}
|
|
26
33
|
|
|
27
34
|
interface ExistingProfile {
|
|
@@ -108,23 +115,49 @@ async function captureDefinition(
|
|
|
108
115
|
return definition;
|
|
109
116
|
}
|
|
110
117
|
|
|
111
|
-
|
|
118
|
+
const BLANK_OPTION = "blank — start from an empty definition";
|
|
119
|
+
|
|
120
|
+
/** One preset row. The caller identifies the preset by the option's INDEX, so
|
|
121
|
+
* a preset named like another row's text cannot be misread. */
|
|
122
|
+
function presetOption(preset: ProfilePreset): string {
|
|
123
|
+
const hint = preset.definition.description ?? preset.definition.label;
|
|
124
|
+
return hint !== undefined ? `${preset.name} — ${hint}` : preset.name;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/** Create: scope, optional preset, name, then fields. */
|
|
112
128
|
export async function runProfileCreateWizard(
|
|
113
129
|
ui: ProfileWizardUi,
|
|
114
|
-
input: { projectTrusted: boolean },
|
|
130
|
+
input: { projectTrusted: boolean; presets?: readonly ProfilePreset[] },
|
|
115
131
|
): Promise<ProfileWizardResult | undefined> {
|
|
116
132
|
const scopeOptions = input.projectTrusted ? ["global", "project"] : ["global"];
|
|
117
133
|
// A single available scope needs no dialog.
|
|
118
134
|
const scope = scopeOptions.length === 1 ? scopeOptions[0] : await ui.select("write to which catalog?", scopeOptions);
|
|
119
135
|
if (scope === undefined) return undefined;
|
|
120
136
|
|
|
121
|
-
const
|
|
122
|
-
|
|
137
|
+
const presets = input.presets ?? PROFILE_PRESETS;
|
|
138
|
+
let preset: ProfilePreset | undefined;
|
|
139
|
+
if (presets.length > 0) {
|
|
140
|
+
const options = [BLANK_OPTION, ...presets.map(presetOption)];
|
|
141
|
+
const chosen = await ui.select("start from which preset?", options);
|
|
142
|
+
if (chosen === undefined) return undefined;
|
|
143
|
+
preset = presets[options.indexOf(chosen) - 1];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// An empty answer takes the preset's name, so Enter accepts the preset.
|
|
147
|
+
const name = await ui.input("profile name", preset?.name);
|
|
148
|
+
if (name === undefined) return undefined;
|
|
149
|
+
const resolvedName = name.trim().length > 0 ? name.trim() : preset?.name;
|
|
150
|
+
if (resolvedName === undefined) return undefined;
|
|
123
151
|
|
|
124
|
-
const definition = await captureDefinition(ui);
|
|
152
|
+
const definition = await captureDefinition(ui, preset?.definition);
|
|
125
153
|
if (definition === undefined) return undefined;
|
|
126
154
|
|
|
127
|
-
return {
|
|
155
|
+
return {
|
|
156
|
+
scope: scope as CatalogScope,
|
|
157
|
+
name: resolvedName,
|
|
158
|
+
definition,
|
|
159
|
+
...(preset !== undefined ? { preset: preset.name } : {}),
|
|
160
|
+
};
|
|
128
161
|
}
|
|
129
162
|
|
|
130
163
|
/** Edit: fields prefilled from the existing complete definition. */
|