@chankov/agent-skills 0.2.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.
Files changed (178) hide show
  1. package/.claude/commands/{doctor.md → doctor-agent-skills.md} +1 -1
  2. package/.pi/extensions/agent-skills-update-check/README.md +4 -4
  3. package/.pi/prompts/{doctor.md → doctor-agent-skills.md} +1 -1
  4. package/.versions/0.2.0/.claude/commands/{doctor.md → doctor-agent-skills.md} +1 -1
  5. package/.versions/0.2.0/.pi/extensions/agent-skills-update-check/README.md +4 -4
  6. package/.versions/0.2.0/.pi/prompts/{doctor.md → doctor-agent-skills.md} +1 -1
  7. package/.versions/0.2.0/skills/guided-workspace-setup/SKILL.md +40 -2
  8. package/.versions/0.3.0/.claude/commands/build.md +18 -0
  9. package/.versions/0.3.0/.claude/commands/code-simplify.md +22 -0
  10. package/.versions/0.3.0/.claude/commands/design-agent.md +14 -0
  11. package/.versions/0.3.0/.claude/commands/doctor-agent-skills.md +13 -0
  12. package/.versions/0.3.0/.claude/commands/plan.md +16 -0
  13. package/.versions/0.3.0/.claude/commands/prime.md +22 -0
  14. package/.versions/0.3.0/.claude/commands/review.md +16 -0
  15. package/.versions/0.3.0/.claude/commands/setup-agent-skills.md +19 -0
  16. package/.versions/0.3.0/.claude/commands/ship.md +17 -0
  17. package/.versions/0.3.0/.claude/commands/spec.md +15 -0
  18. package/.versions/0.3.0/.claude/commands/test.md +19 -0
  19. package/.versions/0.3.0/.opencode/commands/as-build.md +17 -0
  20. package/.versions/0.3.0/.opencode/commands/as-code-simplify.md +16 -0
  21. package/.versions/0.3.0/.opencode/commands/as-design-agent.md +15 -0
  22. package/.versions/0.3.0/.opencode/commands/as-doctor-agent-skills.md +11 -0
  23. package/.versions/0.3.0/.opencode/commands/as-plan.md +16 -0
  24. package/.versions/0.3.0/.opencode/commands/as-prime.md +22 -0
  25. package/.versions/0.3.0/.opencode/commands/as-review.md +15 -0
  26. package/.versions/0.3.0/.opencode/commands/as-setup-agent-skills.md +11 -0
  27. package/.versions/0.3.0/.opencode/commands/as-ship.md +16 -0
  28. package/.versions/0.3.0/.opencode/commands/as-spec.md +16 -0
  29. package/.versions/0.3.0/.opencode/commands/as-test.md +21 -0
  30. package/.versions/0.3.0/.pi/agents/agent-chain.yaml +49 -0
  31. package/.versions/0.3.0/.pi/agents/bowser.md +19 -0
  32. package/.versions/0.3.0/.pi/agents/pi-pi/agent-expert.md +98 -0
  33. package/.versions/0.3.0/.pi/agents/pi-pi/cli-expert.md +41 -0
  34. package/.versions/0.3.0/.pi/agents/pi-pi/config-expert.md +63 -0
  35. package/.versions/0.3.0/.pi/agents/pi-pi/ext-expert.md +43 -0
  36. package/.versions/0.3.0/.pi/agents/pi-pi/keybinding-expert.md +134 -0
  37. package/.versions/0.3.0/.pi/agents/pi-pi/pi-orchestrator.md +57 -0
  38. package/.versions/0.3.0/.pi/agents/pi-pi/prompt-expert.md +70 -0
  39. package/.versions/0.3.0/.pi/agents/pi-pi/skill-expert.md +42 -0
  40. package/.versions/0.3.0/.pi/agents/pi-pi/theme-expert.md +40 -0
  41. package/.versions/0.3.0/.pi/agents/pi-pi/tui-expert.md +85 -0
  42. package/.versions/0.3.0/.pi/agents/teams.yaml +31 -0
  43. package/.versions/0.3.0/.pi/damage-control-rules.yaml +278 -0
  44. package/.versions/0.3.0/.pi/extensions/agent-skills-update-check/README.md +58 -0
  45. package/.versions/0.3.0/.pi/extensions/agent-skills-update-check/index.ts +161 -0
  46. package/.versions/0.3.0/.pi/extensions/agent-skills-update-check/package.json +6 -0
  47. package/.versions/0.3.0/.pi/extensions/chrome-devtools-mcp/README.md +39 -0
  48. package/.versions/0.3.0/.pi/extensions/chrome-devtools-mcp/index.ts +61 -0
  49. package/.versions/0.3.0/.pi/extensions/chrome-devtools-mcp/package.json +6 -0
  50. package/.versions/0.3.0/.pi/extensions/compact-and-continue/README.md +42 -0
  51. package/.versions/0.3.0/.pi/extensions/compact-and-continue/index.ts +120 -0
  52. package/.versions/0.3.0/.pi/extensions/compact-and-continue/package.json +6 -0
  53. package/.versions/0.3.0/.pi/extensions/mcp-bridge/README.md +46 -0
  54. package/.versions/0.3.0/.pi/extensions/mcp-bridge/index.ts +206 -0
  55. package/.versions/0.3.0/.pi/extensions/mcp-bridge/package.json +6 -0
  56. package/.versions/0.3.0/.pi/extensions/package-lock.json +1143 -0
  57. package/.versions/0.3.0/.pi/extensions/package.json +9 -0
  58. package/.versions/0.3.0/.pi/harnesses/agent-chain/README.md +37 -0
  59. package/.versions/0.3.0/.pi/harnesses/agent-chain/index.ts +795 -0
  60. package/.versions/0.3.0/.pi/harnesses/agent-chain/package.json +6 -0
  61. package/.versions/0.3.0/.pi/harnesses/agent-team/README.md +38 -0
  62. package/.versions/0.3.0/.pi/harnesses/agent-team/index.ts +732 -0
  63. package/.versions/0.3.0/.pi/harnesses/agent-team/package.json +6 -0
  64. package/.versions/0.3.0/.pi/harnesses/coms/README.md +36 -0
  65. package/.versions/0.3.0/.pi/harnesses/coms/index.ts +1595 -0
  66. package/.versions/0.3.0/.pi/harnesses/coms/package.json +6 -0
  67. package/.versions/0.3.0/.pi/harnesses/coms-net/README.md +46 -0
  68. package/.versions/0.3.0/.pi/harnesses/coms-net/index.ts +1637 -0
  69. package/.versions/0.3.0/.pi/harnesses/coms-net/package.json +6 -0
  70. package/.versions/0.3.0/.pi/harnesses/damage-control/README.md +38 -0
  71. package/.versions/0.3.0/.pi/harnesses/damage-control/index.ts +207 -0
  72. package/.versions/0.3.0/.pi/harnesses/damage-control/package.json +6 -0
  73. package/.versions/0.3.0/.pi/harnesses/damage-control-continue/README.md +37 -0
  74. package/.versions/0.3.0/.pi/harnesses/damage-control-continue/index.ts +234 -0
  75. package/.versions/0.3.0/.pi/harnesses/damage-control-continue/package.json +6 -0
  76. package/.versions/0.3.0/.pi/harnesses/minimal/README.md +27 -0
  77. package/.versions/0.3.0/.pi/harnesses/minimal/index.ts +32 -0
  78. package/.versions/0.3.0/.pi/harnesses/minimal/package.json +6 -0
  79. package/.versions/0.3.0/.pi/harnesses/package-lock.json +35 -0
  80. package/.versions/0.3.0/.pi/harnesses/package.json +9 -0
  81. package/.versions/0.3.0/.pi/harnesses/pi-pi/README.md +39 -0
  82. package/.versions/0.3.0/.pi/harnesses/pi-pi/index.ts +631 -0
  83. package/.versions/0.3.0/.pi/harnesses/pi-pi/package.json +6 -0
  84. package/.versions/0.3.0/.pi/harnesses/purpose-gate/README.md +27 -0
  85. package/.versions/0.3.0/.pi/harnesses/purpose-gate/index.ts +82 -0
  86. package/.versions/0.3.0/.pi/harnesses/purpose-gate/package.json +6 -0
  87. package/.versions/0.3.0/.pi/harnesses/session-replay/README.md +28 -0
  88. package/.versions/0.3.0/.pi/harnesses/session-replay/index.ts +214 -0
  89. package/.versions/0.3.0/.pi/harnesses/session-replay/package.json +6 -0
  90. package/.versions/0.3.0/.pi/harnesses/subagent-widget/README.md +36 -0
  91. package/.versions/0.3.0/.pi/harnesses/subagent-widget/index.ts +479 -0
  92. package/.versions/0.3.0/.pi/harnesses/subagent-widget/package.json +6 -0
  93. package/.versions/0.3.0/.pi/harnesses/system-select/README.md +39 -0
  94. package/.versions/0.3.0/.pi/harnesses/system-select/index.ts +165 -0
  95. package/.versions/0.3.0/.pi/harnesses/system-select/package.json +6 -0
  96. package/.versions/0.3.0/.pi/harnesses/tilldone/README.md +35 -0
  97. package/.versions/0.3.0/.pi/harnesses/tilldone/index.ts +724 -0
  98. package/.versions/0.3.0/.pi/harnesses/tilldone/package.json +6 -0
  99. package/.versions/0.3.0/.pi/harnesses/tool-counter/README.md +31 -0
  100. package/.versions/0.3.0/.pi/harnesses/tool-counter/index.ts +100 -0
  101. package/.versions/0.3.0/.pi/harnesses/tool-counter/package.json +6 -0
  102. package/.versions/0.3.0/.pi/harnesses/tool-counter-widget/README.md +27 -0
  103. package/.versions/0.3.0/.pi/harnesses/tool-counter-widget/index.ts +66 -0
  104. package/.versions/0.3.0/.pi/harnesses/tool-counter-widget/package.json +6 -0
  105. package/.versions/0.3.0/.pi/prompts/build.md +24 -0
  106. package/.versions/0.3.0/.pi/prompts/code-simplify.md +22 -0
  107. package/.versions/0.3.0/.pi/prompts/doctor-agent-skills.md +13 -0
  108. package/.versions/0.3.0/.pi/prompts/plan.md +16 -0
  109. package/.versions/0.3.0/.pi/prompts/review.md +16 -0
  110. package/.versions/0.3.0/.pi/prompts/setup-agent-skills.md +19 -0
  111. package/.versions/0.3.0/.pi/prompts/ship.md +17 -0
  112. package/.versions/0.3.0/.pi/prompts/spec.md +15 -0
  113. package/.versions/0.3.0/.pi/prompts/test.md +19 -0
  114. package/.versions/0.3.0/.pi/skills/bowser/SKILL.md +114 -0
  115. package/.versions/0.3.0/.version +1 -0
  116. package/.versions/0.3.0/agents/builder.md +6 -0
  117. package/.versions/0.3.0/agents/code-reviewer.md +93 -0
  118. package/.versions/0.3.0/agents/documenter.md +6 -0
  119. package/.versions/0.3.0/agents/plan-reviewer.md +22 -0
  120. package/.versions/0.3.0/agents/planner.md +6 -0
  121. package/.versions/0.3.0/agents/scout.md +6 -0
  122. package/.versions/0.3.0/agents/security-auditor.md +97 -0
  123. package/.versions/0.3.0/agents/test-engineer.md +89 -0
  124. package/.versions/0.3.0/hooks/SIMPLIFY-IGNORE.md +90 -0
  125. package/.versions/0.3.0/hooks/hooks.json +14 -0
  126. package/.versions/0.3.0/hooks/session-start.sh +74 -0
  127. package/.versions/0.3.0/hooks/simplify-ignore-test.sh +247 -0
  128. package/.versions/0.3.0/hooks/simplify-ignore.sh +302 -0
  129. package/.versions/0.3.0/references/accessibility-checklist.md +159 -0
  130. package/.versions/0.3.0/references/performance-checklist.md +121 -0
  131. package/.versions/0.3.0/references/prompting-patterns.md +380 -0
  132. package/.versions/0.3.0/references/security-checklist.md +134 -0
  133. package/.versions/0.3.0/references/testing-patterns.md +236 -0
  134. package/.versions/0.3.0/skills/api-and-interface-design/SKILL.md +294 -0
  135. package/.versions/0.3.0/skills/browser-testing-with-devtools/SKILL.md +335 -0
  136. package/.versions/0.3.0/skills/ci-cd-and-automation/SKILL.md +390 -0
  137. package/.versions/0.3.0/skills/code-review-and-quality/SKILL.md +347 -0
  138. package/.versions/0.3.0/skills/code-simplification/SKILL.md +331 -0
  139. package/.versions/0.3.0/skills/context-engineering/SKILL.md +291 -0
  140. package/.versions/0.3.0/skills/debugging-and-error-recovery/SKILL.md +300 -0
  141. package/.versions/0.3.0/skills/deprecation-and-migration/SKILL.md +206 -0
  142. package/.versions/0.3.0/skills/designing-agents/SKILL.md +394 -0
  143. package/.versions/0.3.0/skills/designing-agents/pi-harness-authoring.md +213 -0
  144. package/.versions/0.3.0/skills/documentation-and-adrs/SKILL.md +278 -0
  145. package/.versions/0.3.0/skills/frontend-ui-engineering/SKILL.md +322 -0
  146. package/.versions/0.3.0/skills/git-workflow-and-versioning/SKILL.md +316 -0
  147. package/.versions/0.3.0/skills/guided-workspace-setup/SKILL.md +331 -0
  148. package/.versions/0.3.0/skills/idea-refine/SKILL.md +178 -0
  149. package/.versions/0.3.0/skills/idea-refine/examples.md +238 -0
  150. package/.versions/0.3.0/skills/idea-refine/frameworks.md +99 -0
  151. package/.versions/0.3.0/skills/idea-refine/refinement-criteria.md +113 -0
  152. package/.versions/0.3.0/skills/idea-refine/scripts/idea-refine.sh +15 -0
  153. package/.versions/0.3.0/skills/incremental-implementation/SKILL.md +279 -0
  154. package/.versions/0.3.0/skills/performance-optimization/SKILL.md +350 -0
  155. package/.versions/0.3.0/skills/planning-and-task-breakdown/SKILL.md +237 -0
  156. package/.versions/0.3.0/skills/security-and-hardening/SKILL.md +349 -0
  157. package/.versions/0.3.0/skills/shipping-and-launch/SKILL.md +309 -0
  158. package/.versions/0.3.0/skills/source-driven-development/SKILL.md +194 -0
  159. package/.versions/0.3.0/skills/spec-driven-development/SKILL.md +237 -0
  160. package/.versions/0.3.0/skills/test-driven-development/SKILL.md +379 -0
  161. package/.versions/0.3.0/skills/using-agent-skills/SKILL.md +176 -0
  162. package/CHANGELOG.md +72 -0
  163. package/README.md +5 -5
  164. package/bin/cli.js +100 -24
  165. package/bin/lib/bootstrap.js +254 -0
  166. package/bin/lib/doctor.js +1 -1
  167. package/docs/getting-started.md +2 -2
  168. package/docs/npm-install.md +34 -11
  169. package/package.json +1 -1
  170. package/skills/guided-workspace-setup/SKILL.md +40 -2
  171. /package/.claude/commands/{setup.md → setup-agent-skills.md} +0 -0
  172. /package/.opencode/commands/{as-doctor.md → as-doctor-agent-skills.md} +0 -0
  173. /package/.opencode/commands/{as-setup.md → as-setup-agent-skills.md} +0 -0
  174. /package/.pi/prompts/{setup.md → setup-agent-skills.md} +0 -0
  175. /package/.versions/0.2.0/.claude/commands/{setup.md → setup-agent-skills.md} +0 -0
  176. /package/.versions/0.2.0/.opencode/commands/{as-doctor.md → as-doctor-agent-skills.md} +0 -0
  177. /package/.versions/0.2.0/.opencode/commands/{as-setup.md → as-setup-agent-skills.md} +0 -0
  178. /package/.versions/0.2.0/.pi/prompts/{setup.md → setup-agent-skills.md} +0 -0
@@ -0,0 +1,479 @@
1
+ /**
2
+ * Subagent Widget — /sub, /subclear, /subrm, /subcont commands with stacking live widgets
3
+ *
4
+ * Each /sub spawns a background Pi subagent with its own persistent session,
5
+ * enabling conversation continuations via /subcont.
6
+ *
7
+ * Usage: pi -e extensions/subagent-widget.ts
8
+ * Then:
9
+ * /sub list files and summarize — spawn a new subagent
10
+ * /subcont 1 now write tests for it — continue subagent #1's conversation
11
+ * /subrm 2 — remove subagent #2 widget
12
+ * /subclear — clear all subagent widgets
13
+ */
14
+
15
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
16
+ import { DynamicBorder } from "@mariozechner/pi-coding-agent";
17
+ import { Container, Text } from "@mariozechner/pi-tui";
18
+ import { Type } from "@sinclair/typebox";
19
+ const { spawn } = require("child_process") as any;
20
+ import * as fs from "fs";
21
+ import * as os from "os";
22
+ import * as path from "path";
23
+
24
+ interface SubState {
25
+ id: number;
26
+ status: "running" | "done" | "error";
27
+ task: string;
28
+ textChunks: string[];
29
+ toolCount: number;
30
+ elapsed: number;
31
+ sessionFile: string; // persistent JSONL session path — used by /subcont to resume
32
+ turnCount: number; // increments each time /subcont continues this agent
33
+ proc?: any; // active ChildProcess ref (for kill on /subrm)
34
+ }
35
+
36
+ export default function (pi: ExtensionAPI) {
37
+ const agents: Map<number, SubState> = new Map();
38
+ let nextId = 1;
39
+ let widgetCtx: any;
40
+
41
+ // ── Session file helpers ──────────────────────────────────────────────────
42
+
43
+ function makeSessionFile(id: number): string {
44
+ const dir = path.join(os.homedir(), ".pi", "agent", "sessions", "subagents");
45
+ fs.mkdirSync(dir, { recursive: true });
46
+ return path.join(dir, `subagent-${id}-${Date.now()}.jsonl`);
47
+ }
48
+
49
+ // ── Widget rendering ──────────────────────────────────────────────────────
50
+
51
+ function updateWidgets() {
52
+ if (!widgetCtx) return;
53
+
54
+ for (const [id, state] of Array.from(agents.entries())) {
55
+ const key = `sub-${id}`;
56
+ widgetCtx.ui.setWidget(key, (_tui: any, theme: any) => {
57
+ const container = new Container();
58
+ const borderFn = (s: string) => theme.fg("dim", s);
59
+
60
+ container.addChild(new Text("", 0, 0)); // top margin
61
+ container.addChild(new DynamicBorder(borderFn));
62
+ const content = new Text("", 1, 0);
63
+ container.addChild(content);
64
+ container.addChild(new DynamicBorder(borderFn));
65
+
66
+ return {
67
+ render(width: number): string[] {
68
+ const lines: string[] = [];
69
+ const statusColor = state.status === "running" ? "accent"
70
+ : state.status === "done" ? "success" : "error";
71
+ const statusIcon = state.status === "running" ? "●"
72
+ : state.status === "done" ? "✓" : "✗";
73
+
74
+ const taskPreview = state.task.length > 40
75
+ ? state.task.slice(0, 37) + "..."
76
+ : state.task;
77
+
78
+ const turnLabel = state.turnCount > 1
79
+ ? theme.fg("dim", ` · Turn ${state.turnCount}`)
80
+ : "";
81
+
82
+ lines.push(
83
+ theme.fg(statusColor, `${statusIcon} Subagent #${state.id}`) +
84
+ turnLabel +
85
+ theme.fg("dim", ` ${taskPreview}`) +
86
+ theme.fg("dim", ` (${Math.round(state.elapsed / 1000)}s)`) +
87
+ theme.fg("dim", ` | Tools: ${state.toolCount}`)
88
+ );
89
+
90
+ const fullText = state.textChunks.join("");
91
+ const lastLine = fullText.split("\n").filter((l: string) => l.trim()).pop() || "";
92
+ if (lastLine) {
93
+ const trimmed = lastLine.length > width - 10
94
+ ? lastLine.slice(0, width - 13) + "..."
95
+ : lastLine;
96
+ lines.push(theme.fg("muted", ` ${trimmed}`));
97
+ }
98
+
99
+ content.setText(lines.join("\n"));
100
+ return container.render(width);
101
+ },
102
+ invalidate() {
103
+ container.invalidate();
104
+ },
105
+ };
106
+ });
107
+ }
108
+ }
109
+
110
+ // ── Streaming helpers ─────────────────────────────────────────────────────
111
+
112
+ function processLine(state: SubState, line: string) {
113
+ if (!line.trim()) return;
114
+ try {
115
+ const event = JSON.parse(line);
116
+ const type = event.type;
117
+
118
+ if (type === "message_update") {
119
+ const delta = event.assistantMessageEvent;
120
+ if (delta?.type === "text_delta") {
121
+ state.textChunks.push(delta.delta || "");
122
+ updateWidgets();
123
+ }
124
+ } else if (type === "tool_execution_start") {
125
+ state.toolCount++;
126
+ updateWidgets();
127
+ }
128
+ } catch {}
129
+ }
130
+
131
+ function spawnAgent(
132
+ state: SubState,
133
+ prompt: string,
134
+ ctx: any,
135
+ ): Promise<void> {
136
+ const model = ctx.model
137
+ ? `${ctx.model.provider}/${ctx.model.id}`
138
+ : "openrouter/google/gemini-3-flash-preview";
139
+
140
+ return new Promise<void>((resolve) => {
141
+ const proc = spawn("pi", [
142
+ "--mode", "json",
143
+ "-p",
144
+ "--session", state.sessionFile, // persistent session for /subcont resumption
145
+ "--no-extensions",
146
+ "--model", model,
147
+ "--tools", "read,bash,grep,find,ls",
148
+ "--thinking", "off",
149
+ prompt,
150
+ ], {
151
+ stdio: ["ignore", "pipe", "pipe"],
152
+ env: { ...process.env },
153
+ });
154
+
155
+ state.proc = proc;
156
+
157
+ const startTime = Date.now();
158
+ const timer = setInterval(() => {
159
+ state.elapsed = Date.now() - startTime;
160
+ updateWidgets();
161
+ }, 1000);
162
+
163
+ let buffer = "";
164
+
165
+ proc.stdout!.setEncoding("utf-8");
166
+ proc.stdout!.on("data", (chunk: string) => {
167
+ buffer += chunk;
168
+ const lines = buffer.split("\n");
169
+ buffer = lines.pop() || "";
170
+ for (const line of lines) processLine(state, line);
171
+ });
172
+
173
+ proc.stderr!.setEncoding("utf-8");
174
+ proc.stderr!.on("data", (chunk: string) => {
175
+ if (chunk.trim()) {
176
+ state.textChunks.push(chunk);
177
+ updateWidgets();
178
+ }
179
+ });
180
+
181
+ proc.on("close", (code) => {
182
+ if (buffer.trim()) processLine(state, buffer);
183
+ clearInterval(timer);
184
+ state.elapsed = Date.now() - startTime;
185
+ state.status = code === 0 ? "done" : "error";
186
+ state.proc = undefined;
187
+ updateWidgets();
188
+
189
+ const result = state.textChunks.join("");
190
+ ctx.ui.notify(
191
+ `Subagent #${state.id} ${state.status} in ${Math.round(state.elapsed / 1000)}s`,
192
+ state.status === "done" ? "success" : "error"
193
+ );
194
+
195
+ pi.sendMessage({
196
+ customType: "subagent-result",
197
+ content: `Subagent #${state.id}${state.turnCount > 1 ? ` (Turn ${state.turnCount})` : ""} finished "${prompt}" in ${Math.round(state.elapsed / 1000)}s.\n\nResult:\n${result.slice(0, 8000)}${result.length > 8000 ? "\n\n... [truncated]" : ""}`,
198
+ display: true,
199
+ }, { deliverAs: "followUp", triggerTurn: true });
200
+
201
+ resolve();
202
+ });
203
+
204
+ proc.on("error", (err) => {
205
+ clearInterval(timer);
206
+ state.status = "error";
207
+ state.proc = undefined;
208
+ state.textChunks.push(`Error: ${err.message}`);
209
+ updateWidgets();
210
+ resolve();
211
+ });
212
+ });
213
+ }
214
+
215
+ // ── Tools for the Main Agent ──────────────────────────────────────────────
216
+
217
+ pi.registerTool({
218
+ name: "subagent_create",
219
+ description: "Spawn a background subagent to perform a task. Returns the subagent ID immediately while it runs in the background. Results will be delivered as a follow-up message when finished.",
220
+ parameters: Type.Object({
221
+ task: Type.String({ description: "The complete task description for the subagent to perform" }),
222
+ }),
223
+ execute: async (callId, args, _signal, _onUpdate, ctx) => {
224
+ widgetCtx = ctx;
225
+ const id = nextId++;
226
+ const state: SubState = {
227
+ id,
228
+ status: "running",
229
+ task: args.task,
230
+ textChunks: [],
231
+ toolCount: 0,
232
+ elapsed: 0,
233
+ sessionFile: makeSessionFile(id),
234
+ turnCount: 1,
235
+ };
236
+ agents.set(id, state);
237
+ updateWidgets();
238
+
239
+ // Fire-and-forget
240
+ spawnAgent(state, args.task, ctx);
241
+
242
+ return {
243
+ content: [{ type: "text", text: `Subagent #${id} spawned and running in background.` }],
244
+ };
245
+ },
246
+ });
247
+
248
+ pi.registerTool({
249
+ name: "subagent_continue",
250
+ description: "Continue an existing subagent's conversation. Use this to give further instructions to a finished subagent. Returns immediately while it runs in the background.",
251
+ parameters: Type.Object({
252
+ id: Type.Number({ description: "The ID of the subagent to continue" }),
253
+ prompt: Type.String({ description: "The follow-up prompt or new instructions" }),
254
+ }),
255
+ execute: async (callId, args, _signal, _onUpdate, ctx) => {
256
+ widgetCtx = ctx;
257
+ const state = agents.get(args.id);
258
+ if (!state) {
259
+ return { content: [{ type: "text", text: `Error: No subagent #${args.id} found.` }] };
260
+ }
261
+ if (state.status === "running") {
262
+ return { content: [{ type: "text", text: `Error: Subagent #${args.id} is still running.` }] };
263
+ }
264
+
265
+ state.status = "running";
266
+ state.task = args.prompt;
267
+ state.textChunks = [];
268
+ state.elapsed = 0;
269
+ state.turnCount++;
270
+ updateWidgets();
271
+
272
+ ctx.ui.notify(`Continuing Subagent #${args.id} (Turn ${state.turnCount})…`, "info");
273
+ spawnAgent(state, args.prompt, ctx);
274
+
275
+ return {
276
+ content: [{ type: "text", text: `Subagent #${args.id} continuing conversation in background.` }],
277
+ };
278
+ },
279
+ });
280
+
281
+ pi.registerTool({
282
+ name: "subagent_remove",
283
+ description: "Remove a specific subagent. Kills it if it's currently running.",
284
+ parameters: Type.Object({
285
+ id: Type.Number({ description: "The ID of the subagent to remove" }),
286
+ }),
287
+ execute: async (callId, args, _signal, _onUpdate, ctx) => {
288
+ widgetCtx = ctx;
289
+ const state = agents.get(args.id);
290
+ if (!state) {
291
+ return { content: [{ type: "text", text: `Error: No subagent #${args.id} found.` }] };
292
+ }
293
+
294
+ if (state.proc && state.status === "running") {
295
+ state.proc.kill("SIGTERM");
296
+ }
297
+ ctx.ui.setWidget(`sub-${args.id}`, undefined);
298
+ agents.delete(args.id);
299
+
300
+ return {
301
+ content: [{ type: "text", text: `Subagent #${args.id} removed successfully.` }],
302
+ };
303
+ },
304
+ });
305
+
306
+ pi.registerTool({
307
+ name: "subagent_list",
308
+ description: "List all active and finished subagents, showing their IDs, tasks, and status.",
309
+ parameters: Type.Object({}),
310
+ execute: async () => {
311
+ if (agents.size === 0) {
312
+ return { content: [{ type: "text", text: "No active subagents." }] };
313
+ }
314
+
315
+ const list = Array.from(agents.values()).map(s =>
316
+ `#${s.id} [${s.status.toUpperCase()}] (Turn ${s.turnCount}) - ${s.task}`
317
+ ).join("\n");
318
+
319
+ return {
320
+ content: [{ type: "text", text: `Subagents:\n${list}` }],
321
+ };
322
+ },
323
+ });
324
+
325
+
326
+
327
+ // ── /sub <task> ───────────────────────────────────────────────────────────
328
+
329
+ pi.registerCommand("sub", {
330
+ description: "Spawn a subagent with live widget: /sub <task>",
331
+ handler: async (args, ctx) => {
332
+ widgetCtx = ctx;
333
+
334
+ const task = args?.trim();
335
+ if (!task) {
336
+ ctx.ui.notify("Usage: /sub <task>", "error");
337
+ return;
338
+ }
339
+
340
+ const id = nextId++;
341
+ const state: SubState = {
342
+ id,
343
+ status: "running",
344
+ task,
345
+ textChunks: [],
346
+ toolCount: 0,
347
+ elapsed: 0,
348
+ sessionFile: makeSessionFile(id),
349
+ turnCount: 1,
350
+ };
351
+ agents.set(id, state);
352
+ updateWidgets();
353
+
354
+ // Fire-and-forget
355
+ spawnAgent(state, task, ctx);
356
+ },
357
+ });
358
+
359
+ // ── /subcont <number> <prompt> ────────────────────────────────────────────
360
+
361
+ pi.registerCommand("subcont", {
362
+ description: "Continue an existing subagent's conversation: /subcont <number> <prompt>",
363
+ handler: async (args, ctx) => {
364
+ widgetCtx = ctx;
365
+
366
+ const trimmed = args?.trim() ?? "";
367
+ const spaceIdx = trimmed.indexOf(" ");
368
+ if (spaceIdx === -1) {
369
+ ctx.ui.notify("Usage: /subcont <number> <prompt>", "error");
370
+ return;
371
+ }
372
+
373
+ const num = parseInt(trimmed.slice(0, spaceIdx), 10);
374
+ const prompt = trimmed.slice(spaceIdx + 1).trim();
375
+
376
+ if (isNaN(num) || !prompt) {
377
+ ctx.ui.notify("Usage: /subcont <number> <prompt>", "error");
378
+ return;
379
+ }
380
+
381
+ const state = agents.get(num);
382
+ if (!state) {
383
+ ctx.ui.notify(`No subagent #${num} found. Use /sub to create one.`, "error");
384
+ return;
385
+ }
386
+
387
+ if (state.status === "running") {
388
+ ctx.ui.notify(`Subagent #${num} is still running — wait for it to finish first.`, "warning");
389
+ return;
390
+ }
391
+
392
+ // Resume: update state for a new turn
393
+ state.status = "running";
394
+ state.task = prompt;
395
+ state.textChunks = [];
396
+ state.elapsed = 0;
397
+ state.turnCount++;
398
+ updateWidgets();
399
+
400
+ ctx.ui.notify(`Continuing Subagent #${num} (Turn ${state.turnCount})…`, "info");
401
+
402
+ // Fire-and-forget — reuses the same sessionFile for conversation history
403
+ spawnAgent(state, prompt, ctx);
404
+ },
405
+ });
406
+
407
+ // ── /subrm <number> ───────────────────────────────────────────────────────
408
+
409
+ pi.registerCommand("subrm", {
410
+ description: "Remove a specific subagent widget: /subrm <number>",
411
+ handler: async (args, ctx) => {
412
+ widgetCtx = ctx;
413
+
414
+ const num = parseInt(args?.trim() ?? "", 10);
415
+ if (isNaN(num)) {
416
+ ctx.ui.notify("Usage: /subrm <number>", "error");
417
+ return;
418
+ }
419
+
420
+ const state = agents.get(num);
421
+ if (!state) {
422
+ ctx.ui.notify(`No subagent #${num} found.`, "error");
423
+ return;
424
+ }
425
+
426
+ // Kill the process if still running
427
+ if (state.proc && state.status === "running") {
428
+ state.proc.kill("SIGTERM");
429
+ ctx.ui.notify(`Subagent #${num} killed and removed.`, "warning");
430
+ } else {
431
+ ctx.ui.notify(`Subagent #${num} removed.`, "info");
432
+ }
433
+
434
+ ctx.ui.setWidget(`sub-${num}`, undefined);
435
+ agents.delete(num);
436
+ },
437
+ });
438
+
439
+ // ── /subclear ─────────────────────────────────────────────────────────────
440
+
441
+ pi.registerCommand("subclear", {
442
+ description: "Clear all subagent widgets",
443
+ handler: async (_args, ctx) => {
444
+ widgetCtx = ctx;
445
+
446
+ let killed = 0;
447
+ for (const [id, state] of Array.from(agents.entries())) {
448
+ if (state.proc && state.status === "running") {
449
+ state.proc.kill("SIGTERM");
450
+ killed++;
451
+ }
452
+ ctx.ui.setWidget(`sub-${id}`, undefined);
453
+ }
454
+
455
+ const total = agents.size;
456
+ agents.clear();
457
+ nextId = 1;
458
+
459
+ const msg = total === 0
460
+ ? "No subagents to clear."
461
+ : `Cleared ${total} subagent${total !== 1 ? "s" : ""}${killed > 0 ? ` (${killed} killed)` : ""}.`;
462
+ ctx.ui.notify(msg, total === 0 ? "info" : "success");
463
+ },
464
+ });
465
+
466
+ // ── Session lifecycle ─────────────────────────────────────────────────────
467
+
468
+ pi.on("session_start", async (_event, ctx) => {
469
+ for (const [id, state] of Array.from(agents.entries())) {
470
+ if (state.proc && state.status === "running") {
471
+ state.proc.kill("SIGTERM");
472
+ }
473
+ ctx.ui.setWidget(`sub-${id}`, undefined);
474
+ }
475
+ agents.clear();
476
+ nextId = 1;
477
+ widgetCtx = ctx;
478
+ });
479
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "agent-skills-pi-subagent-widget",
3
+ "private": true,
4
+ "type": "module",
5
+ "main": "index.ts"
6
+ }
@@ -0,0 +1,39 @@
1
+ # system-select
2
+
3
+ Switch the system prompt via `/system`.
4
+
5
+ > Ported from [`pi-vs-claude-code`](https://github.com/disler/pi-vs-claude-code) by [disler](https://github.com/disler) (MIT). See the [extension catalog](../../../docs/pi-extensions.md).
6
+
7
+ ## What it does
8
+
9
+ Scans `.pi/agents/`, `.claude/agents/`, `.gemini/agents/`, and `.codex/agents/` — both
10
+ project-local and global — for agent definition `.md` files. `/system` opens a select
11
+ dialog to pick one; the chosen agent's body is prepended to pi's default instructions so
12
+ tool usage still works, and tools are restricted to the agent's declared tool set when
13
+ one is specified.
14
+
15
+ ## Commands & tools
16
+
17
+ - `/system` — open the agent-persona picker
18
+
19
+ ## Requires
20
+
21
+ - Agent definition `.md` files in any scanned directory. This repo ships a set under
22
+ `.pi/agents/` (`scout`, `planner`, `builder`, `reviewer`, `documenter`, `red-team`, …).
23
+
24
+ ## Usage
25
+
26
+ ```bash
27
+ pi -e .pi/harnesses/system-select/index.ts
28
+ ```
29
+
30
+ Pairs well with `minimal` as a lightweight footer:
31
+
32
+ ```bash
33
+ pi -e .pi/harnesses/system-select/index.ts -e .pi/harnesses/minimal/index.ts
34
+ ```
35
+
36
+ ## Upstream changes
37
+
38
+ - Theme integration removed — the `themeMap.ts` import and the `applyExtensionDefaults()`
39
+ call were stripped (this repo does not ship pi themes).
@@ -0,0 +1,165 @@
1
+ /**
2
+ * System Select — Switch the system prompt via /system
3
+ *
4
+ * Scans .pi/agents/, .claude/agents/, .gemini/agents/, .codex/agents/
5
+ * (project-local and global) for agent definition .md files.
6
+ *
7
+ * /system opens a select dialog to pick a system prompt. The selected
8
+ * agent's body is prepended to Pi's default instructions so tool usage
9
+ * still works. Tools are restricted to the agent's declared tool set
10
+ * if specified.
11
+ *
12
+ * Usage: pi -e extensions/system-select.ts -e extensions/minimal.ts
13
+ */
14
+
15
+ import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
16
+ import { readdirSync, readFileSync, existsSync } from "node:fs";
17
+ import { join, basename } from "node:path";
18
+ import { homedir } from "node:os";
19
+
20
+ interface AgentDef {
21
+ name: string;
22
+ description: string;
23
+ tools: string[];
24
+ body: string;
25
+ source: string;
26
+ }
27
+
28
+ function parseFrontmatter(raw: string): { fields: Record<string, string>; body: string } {
29
+ const match = raw.match(/^---\s*\n([\s\S]*?)\n---\s*\n([\s\S]*)$/);
30
+ if (!match) return { fields: {}, body: raw };
31
+ const fields: Record<string, string> = {};
32
+ for (const line of match[1].split("\n")) {
33
+ const idx = line.indexOf(":");
34
+ if (idx > 0) fields[line.slice(0, idx).trim()] = line.slice(idx + 1).trim();
35
+ }
36
+ return { fields, body: match[2] };
37
+ }
38
+
39
+ function scanAgents(dir: string, source: string): AgentDef[] {
40
+ if (!existsSync(dir)) return [];
41
+ const agents: AgentDef[] = [];
42
+ try {
43
+ for (const file of readdirSync(dir)) {
44
+ if (!file.endsWith(".md")) continue;
45
+ const raw = readFileSync(join(dir, file), "utf-8");
46
+ const { fields, body } = parseFrontmatter(raw);
47
+ agents.push({
48
+ name: fields.name || basename(file, ".md"),
49
+ description: fields.description || "",
50
+ tools: fields.tools ? fields.tools.split(",").map((t) => t.trim()) : [],
51
+ body: body.trim(),
52
+ source,
53
+ });
54
+ }
55
+ } catch {}
56
+ return agents;
57
+ }
58
+
59
+ function displayName(name: string): string {
60
+ return name.split("-").map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(" ");
61
+ }
62
+
63
+ export default function (pi: ExtensionAPI) {
64
+ let activeAgent: AgentDef | null = null;
65
+ let allAgents: AgentDef[] = [];
66
+ let defaultTools: string[] = [];
67
+
68
+ pi.on("session_start", async (_event, ctx) => {
69
+ activeAgent = null;
70
+ allAgents = [];
71
+
72
+ const home = homedir();
73
+ const cwd = ctx.cwd;
74
+
75
+ const dirs: [string, string][] = [
76
+ [join(cwd, ".pi", "agents"), ".pi"],
77
+ [join(cwd, ".claude", "agents"), ".claude"],
78
+ [join(cwd, ".gemini", "agents"), ".gemini"],
79
+ [join(cwd, ".codex", "agents"), ".codex"],
80
+ [join(home, ".pi", "agent", "agents"), "~/.pi"],
81
+ [join(home, ".claude", "agents"), "~/.claude"],
82
+ [join(home, ".gemini", "agents"), "~/.gemini"],
83
+ [join(home, ".codex", "agents"), "~/.codex"],
84
+ ];
85
+
86
+ const seen = new Set<string>();
87
+ const sourceCounts: Record<string, number> = {};
88
+
89
+ for (const [dir, source] of dirs) {
90
+ const agents = scanAgents(dir, source);
91
+ for (const agent of agents) {
92
+ const key = agent.name.toLowerCase();
93
+ if (seen.has(key)) continue;
94
+ seen.add(key);
95
+ allAgents.push(agent);
96
+ sourceCounts[source] = (sourceCounts[source] || 0) + 1;
97
+ }
98
+ }
99
+
100
+ defaultTools = pi.getActiveTools();
101
+ ctx.ui.setStatus("system-prompt", "System Prompt: Default");
102
+
103
+ const defaultPrompt = ctx.getSystemPrompt();
104
+ const lines = defaultPrompt.split("\n").length;
105
+ const chars = defaultPrompt.length;
106
+
107
+ const loadedSources = Object.entries(sourceCounts)
108
+ .map(([src, count]) => `${count} from ${src}`)
109
+ .join(", ");
110
+
111
+ const notifyLines = [];
112
+ if (allAgents.length > 0) {
113
+ notifyLines.push(`Loaded ${allAgents.length} agents (${loadedSources})`);
114
+ }
115
+ notifyLines.push(`System Prompt: Default (${lines} lines, ${chars} chars)`);
116
+
117
+ ctx.ui.notify(notifyLines.join("\n"), "info");
118
+ });
119
+
120
+ pi.registerCommand("system", {
121
+ description: "Select a system prompt from discovered agents",
122
+ handler: async (_args, ctx) => {
123
+ if (allAgents.length === 0) {
124
+ ctx.ui.notify("No agents found in .*/agents/*.md", "warning");
125
+ return;
126
+ }
127
+
128
+ const options = [
129
+ "Reset to Default",
130
+ ...allAgents.map((a) => `${a.name} — ${a.description} [${a.source}]`),
131
+ ];
132
+
133
+ const choice = await ctx.ui.select("Select System Prompt", options);
134
+ if (choice === undefined) return;
135
+
136
+ if (choice === options[0]) {
137
+ activeAgent = null;
138
+ pi.setActiveTools(defaultTools);
139
+ ctx.ui.setStatus("system-prompt", "System Prompt: Default");
140
+ ctx.ui.notify("System Prompt reset to Default", "success");
141
+ return;
142
+ }
143
+
144
+ const idx = options.indexOf(choice) - 1;
145
+ const agent = allAgents[idx];
146
+ activeAgent = agent;
147
+
148
+ if (agent.tools.length > 0) {
149
+ pi.setActiveTools(agent.tools);
150
+ } else {
151
+ pi.setActiveTools(defaultTools);
152
+ }
153
+
154
+ ctx.ui.setStatus("system-prompt", `System Prompt: ${displayName(agent.name)}`);
155
+ ctx.ui.notify(`System Prompt switched to: ${displayName(agent.name)}`, "success");
156
+ },
157
+ });
158
+
159
+ pi.on("before_agent_start", async (event, _ctx) => {
160
+ if (!activeAgent) return;
161
+ return {
162
+ systemPrompt: activeAgent.body + "\n\n" + event.systemPrompt,
163
+ };
164
+ });
165
+ }
@@ -0,0 +1,6 @@
1
+ {
2
+ "name": "agent-skills-pi-system-select",
3
+ "private": true,
4
+ "type": "module",
5
+ "main": "index.ts"
6
+ }