pi-agents-switch 0.2.7 → 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 CHANGED
@@ -53,7 +53,7 @@ The Builder will design the agent, write the AGENTS.md, and set everything up.
53
53
  ```
54
54
  ~/.pi/agents/<name>/
55
55
  ├── AGENTS.md ← YAML frontmatter + system prompt (all-in-one)
56
- ├── skills/ ← agent-specific skills (optional)
56
+ ├── skills/ ← agent-specific skills (auto-loaded)
57
57
  └── prompts/ ← agent-specific prompt templates (optional)
58
58
  ```
59
59
 
@@ -100,7 +100,7 @@ Each agent starts with PI's current config, then applies:
100
100
  2. **Add** items listed in `tools` / `skills`
101
101
  3. If an item appears in **both** `tools` and `excluded_tools`, `tools` wins (explicit include beats exclude)
102
102
 
103
- Agent-specific folders (`skills/`, `prompts/`) are always included in addition to inherited items.
103
+ Agent-specific skills (`skills/` directory) are auto-discovered and always included.
104
104
 
105
105
  ### Fallback Chain
106
106
 
@@ -150,7 +150,7 @@ Agents are auto-discovered from the filesystem — no registration needed.
150
150
  {
151
151
  "version": 1,
152
152
  "hotkey": "f9",
153
- "active": "PI"
153
+ "default": "PI"
154
154
  }
155
155
  ```
156
156
 
package/frontmatter.ts CHANGED
@@ -172,7 +172,7 @@ export function serializeFrontmatter(fm: AgentFrontmatter): string {
172
172
  writeArray("excluded_tools", fm.excluded_tools);
173
173
 
174
174
  writeArray("skills", fm.skills);
175
- writeArray("excluded_skills", fm.excluded_skills ?? fm.noskills);
175
+ writeArray("excluded_skills", fm.excluded_skills);
176
176
 
177
177
  lines.push("---");
178
178
  lines.push(""); // blank line before body
package/index.ts CHANGED
@@ -157,9 +157,7 @@ Inventory what's available so you can make grounded recommendations:
157
157
  2. **Existing agents**: \`ls ~/.pi/agents/\` (and project \`.pi/agents/\` if applicable) → avoid name collisions
158
158
  3. **Tools currently available**: list names from your system context tool definitions
159
159
  4. **Skills currently available**: check \`<available_skills>\` block in your context, or \`ls ~/.pi/agent/skills/\` and \`ls ~/.agents/skills/\`
160
- 5. **Extensions installed**: \`ls ~/.pi/agent/npm/node_modules/\` for pi-* packages
161
-
162
- **Never** recommend models, tools, or skills the user doesn't have installed.
160
+ \n**Never** recommend models, tools, or skills the user doesn't have installed.
163
161
 
164
162
  ### Phase 1 — Gather requirements (single \`ask_user_question\` call, 4-6 questions)
165
163
 
@@ -225,6 +223,7 @@ export default function agentsSwitch(pi: ExtensionAPI) {
225
223
  let currentAgent: string = PI_AGENT_NAME;
226
224
  let currentResolved: ResolvedAgentConfig | undefined;
227
225
  let piSnapshot: PIStateSnapshot | undefined;
226
+ let sessionInitialized = false;
228
227
 
229
228
  // ─── Helpers ────────────────────────────────────────
230
229
 
@@ -261,7 +260,7 @@ export default function agentsSwitch(pi: ExtensionAPI) {
261
260
  function getPIState(): PIState {
262
261
  return {
263
262
  tools: pi.getActiveTools(),
264
- skills: [],
263
+ skills: pm.getPISkillNames(),
265
264
  };
266
265
  }
267
266
 
@@ -397,22 +396,16 @@ export default function agentsSwitch(pi: ExtensionAPI) {
397
396
  // ─── Agent cycling / switching ──────────────────────
398
397
 
399
398
  async function cycleAgent(ctx?: any): Promise<void> {
400
- const config = pm.loadConfig();
401
399
  const names = [PI_AGENT_NAME, ...pm.getAgentNames()];
402
400
  const idx = names.indexOf(currentAgent);
403
401
  const next = names[(idx + 1) % names.length];
404
- config.active = next === PI_AGENT_NAME ? PI_AGENT_NAME : next;
405
- pm.saveConfig(config);
406
402
  currentAgent = next;
407
403
  await applyAgentSettings(ctx);
408
404
  updateStatus(ctx);
409
405
  }
410
406
 
411
407
  async function switchToAgent(name: string, ctx?: any): Promise<void> {
412
- const config = pm.loadConfig();
413
408
  if (name === PI_AGENT_NAME) {
414
- config.active = PI_AGENT_NAME;
415
- pm.saveConfig(config);
416
409
  currentAgent = PI_AGENT_NAME;
417
410
  await applyAgentSettings(ctx);
418
411
  updateStatus(ctx);
@@ -425,8 +418,6 @@ export default function agentsSwitch(pi: ExtensionAPI) {
425
418
  );
426
419
  return;
427
420
  }
428
- config.active = name;
429
- pm.saveConfig(config);
430
421
  currentAgent = name;
431
422
  await applyAgentSettings(ctx);
432
423
  updateStatus(ctx);
@@ -702,7 +693,6 @@ export default function agentsSwitch(pi: ExtensionAPI) {
702
693
  }
703
694
  }
704
695
  if (count > 0) {
705
- loadCurrentAgent(pm.loadConfig());
706
696
  ctx.ui.notify(`Initialized ${count} default agent(s)`, "info");
707
697
  } else {
708
698
  ctx.ui.notify("Default agents already initialized", "info");
@@ -728,7 +718,6 @@ export default function agentsSwitch(pi: ExtensionAPI) {
728
718
 
729
719
  pi.on("before_agent_start", async (event, ctx) => {
730
720
  initPM(ctx.cwd);
731
- loadCurrentAgent(pm.loadConfig());
732
721
  updateStatus(ctx);
733
722
 
734
723
  if (currentAgent === PI_AGENT_NAME) return;
@@ -808,7 +797,6 @@ export default function agentsSwitch(pi: ExtensionAPI) {
808
797
  const payload = event.payload as Record<string, unknown>;
809
798
  let changed = false;
810
799
 
811
- // Temperature / topP
812
800
  if (currentResolved.temperature !== undefined) {
813
801
  payload.temperature = currentResolved.temperature;
814
802
  changed = true;
@@ -818,12 +806,8 @@ export default function agentsSwitch(pi: ExtensionAPI) {
818
806
  changed = true;
819
807
  }
820
808
 
821
- // ---- Sanitization: prevent other extensions from contaminating ----
822
- // Other extensions (e.g. pi-lens) may override our tools and prompt
823
- // in before_agent_start (last-runner-wins). We re-apply our config
824
- // here as the final defence before the API call.
825
-
826
- // 1. Filter tools — only allow resolved tools
809
+ // Filter tools at the API level last defence against other
810
+ // extensions (e.g. pi-lens) overriding our tool set.
827
811
  const allowedTools = new Set(currentResolved.tools);
828
812
  const rawTools = payload.tools as
829
813
  | Array<{ function?: { name?: string } }>
@@ -836,113 +820,6 @@ export default function agentsSwitch(pi: ExtensionAPI) {
836
820
  if ((payload.tools as Array<any>).length !== before) changed = true;
837
821
  }
838
822
 
839
- // 2. Clean system message — replace contaminated sections
840
- const messages = payload.messages as
841
- | Array<{ role: string; content: string }>
842
- | undefined;
843
- if (messages && messages.length > 0 && messages[0]?.role === "system") {
844
- let content: string = messages[0].content;
845
- const originalLen = content.length;
846
-
847
- // Strip the "Available tools:" block (other extensions may inject their own)
848
- // Format: "Available tools:\n- name: desc\n..."
849
- // We replace it with our clean version.
850
- const toolsMarker = "\nAvailable tools:";
851
- const toolsIdx = content.indexOf(toolsMarker);
852
- if (toolsIdx >= 0) {
853
- // Find where the tools block ends (next section or empty line before next section)
854
- const nextSection = content.indexOf(
855
- "\nIn addition to the tools above",
856
- toolsIdx,
857
- );
858
- if (nextSection >= 0) {
859
- // Build clean tools list
860
- if (currentResolved.tools.length === 0) {
861
- content =
862
- content.substring(0, toolsIdx) +
863
- "\nAvailable tools:\n(none)\n" +
864
- content.substring(nextSection);
865
- } else {
866
- // Rebuild clean tools from tool snippets if available
867
- const snippets = pi.getAllTools();
868
- const cleanLines = currentResolved.tools
869
- .map((name) => {
870
- const tool = snippets.find((t) => t.name === name);
871
- return tool
872
- ? `- ${tool.name}: ${tool.description ?? ""}`
873
- : `- ${name}`;
874
- })
875
- .join("\n");
876
- content =
877
- content.substring(0, toolsIdx) +
878
- `\nAvailable tools:\n${cleanLines}\n` +
879
- content.substring(nextSection);
880
- }
881
- }
882
- }
883
-
884
- // Replace <available_skills> block: only touch skills when the agent
885
- // has explicit skill config (skillNames.length > 0).
886
- // For agents without skill config, leave Pi's default skills intact.
887
- if (currentResolved.skillNames.length > 0) {
888
- const skillsOpen = "\n<available_skills>";
889
- const skillsIdx = content.indexOf(skillsOpen);
890
- if (skillsIdx >= 0) {
891
- const skillsEnd = content.indexOf(
892
- "\n</available_skills>",
893
- skillsIdx,
894
- );
895
- if (skillsEnd >= 0) {
896
- content =
897
- content.substring(0, skillsIdx) +
898
- content.substring(
899
- skillsEnd + "\n</available_skills>".length,
900
- );
901
- }
902
- }
903
-
904
- // Rebuild clean skills block from our resolved config
905
- const selectedSkills = pm.getSkillObjects(
906
- currentResolved.skillNames,
907
- );
908
- if (selectedSkills.length > 0) {
909
- const skillsBlock = formatSkillsBlock(selectedSkills);
910
- // Insert before "Current date:"
911
- const currentDateMarker = "\nCurrent date:";
912
- const idx = content.indexOf(currentDateMarker);
913
- if (idx >= 0) {
914
- content =
915
- content.substring(0, idx) +
916
- skillsBlock +
917
- content.substring(idx);
918
- }
919
- }
920
- }
921
-
922
- // Strip "Pi documentation" block (pi-lens may re-add after our handler strips it)
923
- const piDocsMarker =
924
- "Pi documentation (read only when the user asks about pi itself";
925
- const piDocsStart = content.indexOf(piDocsMarker);
926
- if (piDocsStart >= 0) {
927
- const currentDateMarker = "\nCurrent date:";
928
- const currentDateStart = content.indexOf(
929
- currentDateMarker,
930
- piDocsStart,
931
- );
932
- if (currentDateStart >= 0) {
933
- content =
934
- content.substring(0, piDocsStart) +
935
- content.substring(currentDateStart);
936
- }
937
- }
938
-
939
- if (content.length !== originalLen) {
940
- messages[0].content = content;
941
- changed = true;
942
- }
943
- }
944
-
945
- // Return payload only if we changed something (otherwise Pi uses default)
946
823
  if (changed) return payload;
947
824
  });
948
825
 
@@ -950,8 +827,15 @@ export default function agentsSwitch(pi: ExtensionAPI) {
950
827
 
951
828
  pi.on("session_start", async (_event, ctx) => {
952
829
  initPM(ctx.cwd);
953
- const config = pm.loadConfig();
954
- loadCurrentAgent(config);
830
+
831
+ // Only restore from config on the very first session_start
832
+ // within this process. After that, use in-memory state.
833
+ if (!sessionInitialized) {
834
+ const config = pm.loadConfig();
835
+ if (config.default) loadCurrentAgent(config);
836
+ sessionInitialized = true;
837
+ }
838
+
955
839
  // Apply agent settings immediately so tools/model/thinking
956
840
  // are set before the first prompt builds the system prompt.
957
841
  await applyAgentSettings(ctx);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-agents-switch",
3
- "version": "0.2.7",
3
+ "version": "0.3.0",
4
4
  "description": "Tab to switch primary agents in Pi — like OpenCode's agent switching. Each agent gets an isolated profile with its own AGENTS.md, extensions, skills, and settings.",
5
5
  "type": "module",
6
6
  "pi": {
@@ -8,7 +8,6 @@
8
8
  * Profile dir layout:
9
9
  * ~/.pi/agents/<name>/ ← user-level agent
10
10
  * ├── AGENTS.md ← YAML frontmatter + system prompt
11
- * ├── extensions/ ← agent-specific extensions
12
11
  * ├── skills/ ← agent-specific skills
13
12
  * └── prompts/ ← agent-specific prompt templates
14
13
  *
@@ -21,7 +20,7 @@
21
20
  * 3. PI defaults (from running pi environment)
22
21
  *
23
22
  * Last-write-wins: when the same item appears in both tools and
24
- * excluded_tools (or extensions/excluded_extensions, skills/excluded_skills),
23
+ * excluded_tools (or skills/excluded_skills),
25
24
  * whichever YAML key was declared LAST in the file wins.
26
25
  * `"*"` in any exclude list removes everything except items explicitly added.
27
26
  */
@@ -35,7 +34,7 @@ import {
35
34
  statSync,
36
35
  rmSync,
37
36
  } from "node:fs";
38
- import { join } from "node:path";
37
+ import { join, basename } from "node:path";
39
38
  import { homedir } from "node:os";
40
39
  import { parseFrontmatter, serializeFrontmatter } from "./frontmatter";
41
40
  import type {
@@ -65,6 +64,12 @@ export class ProfileManager {
65
64
  readonly configPath: string;
66
65
  readonly piAgentDir: string;
67
66
 
67
+ /** Cached skill index keyed by name (rebuilt on cwd change) */
68
+ private skillCache: Map<
69
+ string,
70
+ { name: string; description: string; filePath: string; source: string }
71
+ > | null = null;
72
+
68
73
  constructor(private cwd?: string) {
69
74
  const piRoot = join(homedir(), ".pi");
70
75
  this.userAgentsDir = join(piRoot, "agents");
@@ -90,9 +95,14 @@ export class ProfileManager {
90
95
 
91
96
  try {
92
97
  const raw = readFileSync(this.configPath, "utf8");
93
- const cfg = JSON.parse(raw) as AgentsConfig;
98
+ const cfg = JSON.parse(raw) as AgentsConfig & { active?: string };
94
99
  if (cfg.version !== 1) throw new Error("Unsupported config version");
95
- return cfg;
100
+ // Migrate from deprecated `active` to `default`
101
+ if (cfg.active !== undefined && cfg.default === undefined) {
102
+ cfg.default = cfg.active;
103
+ this.saveConfig(cfg as AgentsConfig);
104
+ }
105
+ return cfg as AgentsConfig;
96
106
  } catch (err) {
97
107
  const corruptPath = this.configPath + ".corrupted." + Date.now();
98
108
  try {
@@ -156,7 +166,6 @@ export class ProfileManager {
156
166
  if (existing) {
157
167
  existing.path = path;
158
168
  existing.hasAgentsMd = existsSync(join(path, "AGENTS.md"));
159
- existing.hasExtensions = existsSync(join(path, "extensions"));
160
169
  existing.hasSkills = existsSync(join(path, "skills"));
161
170
  existing.source = "project";
162
171
  } else {
@@ -177,7 +186,6 @@ export class ProfileManager {
177
186
  name,
178
187
  path,
179
188
  hasAgentsMd: existsSync(join(path, "AGENTS.md")),
180
- hasExtensions: existsSync(join(path, "extensions")),
181
189
  hasSkills: existsSync(join(path, "skills")),
182
190
  source,
183
191
  };
@@ -351,24 +359,34 @@ export class ProfileManager {
351
359
  keyOrder,
352
360
  );
353
361
 
354
- // Skills: prefer excluded_skills, fall back to deprecated noskills
355
- const skillExclude = fm.excluded_skills ?? fm.noskills;
356
- const skillExcludeKey =
362
+ // Skills: resolve PI's base, then auto-append agent's own skills
363
+ const agentSkillNames = this.getAgentSkillNames(
364
+ name,
365
+ agentPath,
366
+ projectPath,
367
+ );
368
+ const resolvedSkills =
357
369
  fm.excluded_skills !== undefined
358
- ? "excluded_skills"
359
- : fm.noskills !== undefined
360
- ? "noskills"
361
- : undefined;
362
- const skillPaths = skillExcludeKey
363
- ? this.resolveList(
364
- pi.skills,
365
- skillExclude,
366
- fm.skills,
367
- "skills",
368
- skillExcludeKey,
369
- keyOrder,
370
- )
371
- : [...pi.skills, ...(fm.skills ?? [])];
370
+ ? this.resolveList(
371
+ pi.skills,
372
+ fm.excluded_skills,
373
+ fm.skills,
374
+ "skills",
375
+ "excluded_skills",
376
+ keyOrder,
377
+ )
378
+ : [...pi.skills, ...(fm.skills ?? [])];
379
+
380
+ // Agent skills are always available, unless explicitly excluded by name
381
+ const excludedSet = new Set(fm.excluded_skills ?? []);
382
+ const resolvedSet = new Set(resolvedSkills);
383
+ for (const skill of agentSkillNames) {
384
+ if (!excludedSet.has(skill) && !resolvedSet.has(skill)) {
385
+ resolvedSkills.push(skill);
386
+ }
387
+ }
388
+
389
+ const skillPaths = resolvedSkills;
372
390
 
373
391
  const sourcePath = projectPath ?? agentPath ?? "PI (default)";
374
392
 
@@ -460,6 +478,67 @@ export class ProfileManager {
460
478
  * Look up full skill objects by name from Pi's standard skill directories.
461
479
  * Returns name, description, and filePath for each skill found.
462
480
  */
481
+ /** Get PI skill names (global + extension + project, excluding agent-scoped). */
482
+ getPISkillNames(): string[] {
483
+ if (!this.skillCache) this.rebuildSkillIndex();
484
+ return [...this.skillCache!.entries()]
485
+ .filter(([, v]) => v.source !== "agent")
486
+ .map(([k]) => k);
487
+ }
488
+
489
+ /**
490
+ * Discover skill names from an agent's own skills/ directory.
491
+ * Scans both user-level (~/.pi/agents/<name>/skills/) and
492
+ * project-level (<cwd>/.pi/agents/<name>/skills/) locations.
493
+ */
494
+ getAgentSkillNames(
495
+ name: string,
496
+ agentPath?: string,
497
+ projectPath?: string,
498
+ ): string[] {
499
+ const names = new Set<string>();
500
+
501
+ const scanDir = (dir: string): boolean => {
502
+ if (!existsSync(dir)) return false;
503
+ try {
504
+ for (const entry of readdirSync(dir)) {
505
+ const full = join(dir, entry);
506
+ try {
507
+ if (
508
+ statSync(full).isDirectory() &&
509
+ existsSync(join(full, "SKILL.md"))
510
+ ) {
511
+ names.add(entry);
512
+ }
513
+ } catch {
514
+ // skip
515
+ }
516
+ }
517
+ return true;
518
+ } catch {
519
+ return false;
520
+ }
521
+ };
522
+
523
+ // User-level agent skills
524
+ const userSkillsDir = join(this.agentPath(name), "skills");
525
+ scanDir(userSkillsDir);
526
+
527
+ // Project-level agent skills (if known)
528
+ if (projectPath) {
529
+ scanDir(join(projectPath, "skills"));
530
+ } else if (agentPath && agentPath !== this.agentPath(name)) {
531
+ // agentPath might already be the project path
532
+ scanDir(join(agentPath, "skills"));
533
+ }
534
+
535
+ return [...names];
536
+ }
537
+
538
+ /**
539
+ * Look up full skill objects by name.
540
+ * Scans Pi's standard skill locations recursively (flat + nested).
541
+ */
463
542
  getSkillObjects(
464
543
  skillNames: string[],
465
544
  ): Array<{ name: string; description: string; filePath: string }> {
@@ -480,28 +559,102 @@ export class ProfileManager {
480
559
  private findSkillOnDisk(
481
560
  name: string,
482
561
  ): { name: string; description: string; filePath: string } | undefined {
483
- const locations = [join(this.piAgentDir, "skills", name, "SKILL.md")];
562
+ if (!this.skillCache) {
563
+ this.rebuildSkillIndex();
564
+ }
565
+ return this.skillCache!.get(name);
566
+ }
484
567
 
485
- if (this.cwd) {
486
- locations.push(join(this.cwd, ".pi", "skills", name, "SKILL.md"));
568
+ /**
569
+ * Rebuild the skill index by scanning all known skill directories.
570
+ *
571
+ * Locations scanned:
572
+ * 1. ~/.pi/agent/skills/<name>/SKILL.md (flat, user-installed)
573
+ * 2. ~/.pi/agent/npm/node_modules/<ext>/skills/ (extension skills)
574
+ * 3. ~/.pi/agents/<agent>/skills/ (agent-specific skills)
575
+ * 4. <cwd>/.pi/skills/ (project skills, flat)
576
+ */
577
+ rebuildSkillIndex(): void {
578
+ this.skillCache = new Map();
579
+
580
+ const scanDir = (dir: string, maxDepth: number, source: string): void => {
581
+ if (maxDepth <= 0) return;
582
+
583
+ // Check for SKILL.md in this directory
584
+ const skillMd = join(dir, "SKILL.md");
585
+ if (existsSync(skillMd)) {
586
+ try {
587
+ const content = readFileSync(skillMd, "utf8");
588
+ const { frontmatter } = parseFrontmatter(content);
589
+ const skillName = frontmatter.name ?? basename(dir);
590
+ this.skillCache!.set(skillName, {
591
+ name: skillName,
592
+ description: frontmatter.description ?? "",
593
+ filePath: skillMd,
594
+ source,
595
+ });
596
+ } catch {}
597
+ }
598
+
599
+ try {
600
+ for (const entry of readdirSync(dir)) {
601
+ if (entry === "node_modules") continue;
602
+ const sub = join(dir, entry);
603
+ try {
604
+ if (statSync(sub).isDirectory()) scanDir(sub, maxDepth - 1, source);
605
+ } catch {}
606
+ }
607
+ } catch {}
608
+ };
609
+
610
+ // 1. Global Pi skills
611
+ scanDir(join(this.piAgentDir, "skills"), 2, "global");
612
+
613
+ // 2. Extension skills from npm packages (nested: node_modules/<ext>/skills/<name>/SKILL.md)
614
+ const npmDir = join(this.piAgentDir, "npm", "node_modules");
615
+ if (existsSync(npmDir)) {
616
+ for (const ext of readdirSync(npmDir)) {
617
+ const extSkills = join(npmDir, ext, "skills");
618
+ if (existsSync(extSkills)) {
619
+ try {
620
+ if (statSync(extSkills).isDirectory()) {
621
+ scanDir(extSkills, 3, "extension");
622
+ }
623
+ } catch {
624
+ // Skip
625
+ }
626
+ }
627
+ }
487
628
  }
488
629
 
489
- for (const loc of locations) {
490
- if (!existsSync(loc)) continue;
630
+ // 3. Agent-specific skills (nested: agents/<agent>/skills/<name>/SKILL.md)
631
+ const agentsDir = join(homedir(), ".pi", "agents");
632
+ if (existsSync(agentsDir)) {
491
633
  try {
492
- const content = readFileSync(loc, "utf8");
493
- const { frontmatter } = parseFrontmatter(content);
494
- return {
495
- name: frontmatter.name ?? name,
496
- description: frontmatter.description ?? "",
497
- filePath: loc,
498
- };
634
+ for (const agent of readdirSync(agentsDir)) {
635
+ const agentSkills = join(agentsDir, agent, "skills");
636
+ if (existsSync(agentSkills)) {
637
+ try {
638
+ if (statSync(agentSkills).isDirectory()) {
639
+ scanDir(agentSkills, 3, "agent");
640
+ }
641
+ } catch {
642
+ // Skip
643
+ }
644
+ }
645
+ }
499
646
  } catch {
500
- // Skip unreadable files
647
+ // Skip unreadable agents dir
501
648
  }
502
649
  }
503
650
 
504
- return undefined;
651
+ // 4. Project-level skills (flat)
652
+ if (this.cwd) {
653
+ const projSkillsDir = join(this.cwd, ".pi", "skills");
654
+ if (existsSync(projSkillsDir)) {
655
+ scanDir(projSkillsDir, 2, "project");
656
+ }
657
+ }
505
658
  }
506
659
 
507
660
  // ─── Agent creation / deletion ───────────────────────
@@ -531,7 +684,6 @@ export class ProfileManager {
531
684
  // Build directory structure
532
685
  try {
533
686
  mkdirSync(path, { recursive: true });
534
- mkdirSync(join(path, "extensions"));
535
687
  mkdirSync(join(path, "skills"));
536
688
  mkdirSync(join(path, "prompts"));
537
689
  } catch (err) {
@@ -579,8 +731,8 @@ Edit this file to customize your behavior.
579
731
  rmSync(path, { recursive: true, force: true });
580
732
 
581
733
  const config = this.loadConfig();
582
- if (config.active === name) {
583
- delete config.active;
734
+ if (config.default === name) {
735
+ delete config.default;
584
736
  }
585
737
  this.saveConfig(config);
586
738
  }
@@ -592,8 +744,8 @@ Edit this file to customize your behavior.
592
744
  * Falls back to "PI" if nothing is set or the configured agent no longer exists.
593
745
  */
594
746
  getActive(config: AgentsConfig): string {
595
- if (config.active && this.exists(config.active)) {
596
- return config.active;
747
+ if (config.default && this.exists(config.default)) {
748
+ return config.default;
597
749
  }
598
750
  return "PI";
599
751
  }
package/types.ts CHANGED
@@ -51,11 +51,8 @@ export interface AgentFrontmatter {
51
51
  skills?: string[];
52
52
  /**
53
53
  * Skills to REMOVE (from PI's inherited set). Supports `"*"` wildcard.
54
- * Preferred over deprecated `noskills`.
55
54
  */
56
55
  excluded_skills?: string[];
57
- /** @deprecated Use `excluded_skills` instead. Still recognized for backward compat. */
58
- noskills?: string[];
59
56
  }
60
57
 
61
58
  // ─── Frontmatter parse result ──────────────────────────────
@@ -116,7 +113,7 @@ export interface AgentsConfig {
116
113
  /** Key string like "f9" or "ctrl+shift+a" */
117
114
  hotkey?: KeyId;
118
115
  /** Which agent is currently active ("PI" or a custom agent name) */
119
- active?: string;
116
+ default?: string;
120
117
  }
121
118
 
122
119
  // ─── Disk Profile ──────────────────────────────────────────
@@ -129,8 +126,6 @@ export interface AgentDiskProfile {
129
126
  path: string;
130
127
  /** Has an AGENTS.md file */
131
128
  hasAgentsMd: boolean;
132
- /** Has an extensions/ subdirectory */
133
- hasExtensions: boolean;
134
129
  /** Has a skills/ subdirectory */
135
130
  hasSkills: boolean;
136
131
  /** Agent source scope */