@d3ara1n/pi-command-palette 0.3.0 → 0.4.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 CHANGED
@@ -46,6 +46,8 @@ When a command replaces your current editor text, the original content is saved
46
46
 
47
47
  The "Model: Switch Model" action opens a secondary overlay listing all models with configured API keys. Select one to switch instantly — no need to go through `/model` or `Ctrl+P`.
48
48
 
49
+ **Scoped models float to the top**, marked with a ★ (favorite) prefix. "Scoped" here means the same set pi uses for its built-in selector's scoped tab and `Ctrl+P` cycling — the `enabledModels` patterns in your `settings.json` (project `.pi/settings.json` overrides global `~/.pi/agent/settings.json`). Everything else follows alphabetically. If no scope is configured, the list is a plain alphabetical roster — nothing breaks.
50
+
49
51
  ## Configuration
50
52
 
51
53
  The default shortcut is `Ctrl+Shift+P`. If it conflicts with your terminal, override it via either of the following (evaluated in order, first match wins).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d3ara1n/pi-command-palette",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "description": "Global command palette for pi — press Ctrl+Shift+P to search and run commands from anywhere",
6
6
  "main": "src/index.ts",
package/src/index.ts CHANGED
@@ -13,7 +13,12 @@
13
13
  */
14
14
 
15
15
  import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
16
- import { DynamicBorder } from "@earendil-works/pi-coding-agent";
16
+ import {
17
+ DynamicBorder,
18
+ getAgentDir,
19
+ resolveModelScopeWithDiagnostics,
20
+ SettingsManager,
21
+ } from "@earendil-works/pi-coding-agent";
17
22
  import {
18
23
  Container,
19
24
  type SelectItem,
@@ -150,6 +155,36 @@ function buildPaletteItems(pi: ExtensionAPI, ctx: ExtensionContext): PaletteItem
150
155
 
151
156
  // ── Model selector ─────────────────────────────────────────────────
152
157
 
158
+ const STAR = "★ ";
159
+
160
+ /**
161
+ * Resolve the set of "scoped" model full-ids (`provider/id`) — the same models
162
+ * pi surfaces in its built-in selector's "scoped" tab and Ctrl+P cycling.
163
+ *
164
+ * Fully official-API driven, no manual settings parsing:
165
+ * - `SettingsManager.getEnabledModels()` reads the `enabledModels` scope
166
+ * patterns (global + project merge handled by pi).
167
+ * - `resolveModelScopeWithDiagnostics()` expands those patterns (globs, aliases,
168
+ * thinking-level suffixes) into concrete models — identical to pi's scope tab.
169
+ *
170
+ * Any failure degrades to an empty set: the selector still works, just without
171
+ * the scoped grouping.
172
+ */
173
+ async function resolveScopedModelIds(
174
+ modelRegistry: ExtensionContext["modelRegistry"],
175
+ cwd: string,
176
+ ): Promise<Set<string>> {
177
+ try {
178
+ const settings = SettingsManager.create(cwd, getAgentDir());
179
+ const patterns = settings.getEnabledModels();
180
+ if (!patterns || patterns.length === 0) return new Set();
181
+ const { scopedModels } = await resolveModelScopeWithDiagnostics(patterns, modelRegistry);
182
+ return new Set(scopedModels.map((s) => `${s.model.provider}/${s.model.id}`));
183
+ } catch {
184
+ return new Set();
185
+ }
186
+ }
187
+
153
188
  async function showModelSelector(pi: ExtensionAPI, ctx: ExtensionContext): Promise<void> {
154
189
  let models: Awaited<ReturnType<typeof ctx.modelRegistry.getAvailable>>;
155
190
  try {
@@ -164,10 +199,24 @@ async function showModelSelector(pi: ExtensionAPI, ctx: ExtensionContext): Promi
164
199
  return;
165
200
  }
166
201
 
167
- const items: SelectItem[] = models.map((m) => ({
168
- value: `${m.provider}/${m.id}`,
169
- label: m.name,
170
- description: m.provider,
202
+ const scopedIds = await resolveScopedModelIds(ctx.modelRegistry, ctx.cwd);
203
+
204
+ // Scoped models float to the top with a ★ prefix (favorites); the rest follow
205
+ // alphabetically. Within the scoped group we also sort alphabetically so the
206
+ // ordering is stable and predictable regardless of registry order.
207
+ const decorated = models.map((m) => {
208
+ const value = `${m.provider}/${m.id}`;
209
+ return { model: m, value, scoped: scopedIds.has(value) };
210
+ });
211
+ decorated.sort((a, b) => {
212
+ if (a.scoped !== b.scoped) return a.scoped ? -1 : 1;
213
+ return a.model.name.localeCompare(b.model.name);
214
+ });
215
+
216
+ const items: SelectItem[] = decorated.map((d) => ({
217
+ value: d.value,
218
+ label: d.scoped ? `${STAR}${d.model.name}` : d.model.name,
219
+ description: d.model.provider,
171
220
  }));
172
221
 
173
222
  const result = await ctx.ui.custom<string | null>(