@herbertgao/pi-extensions 2026.8.1 → 2026.8.2

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 (162) hide show
  1. package/README.md +5 -2
  2. package/node_modules/@czottmann/pi-automode/README.md +16 -2
  3. package/node_modules/@czottmann/pi-automode/docs/GLOSSARY.md +35 -0
  4. package/node_modules/@czottmann/pi-automode/docs/automode-classifier-flow.md +18 -9
  5. package/node_modules/@czottmann/pi-automode/docs/defaults.md +6 -2
  6. package/node_modules/@czottmann/pi-automode/examples/automode.local.json +6 -0
  7. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/classifier.ts +11 -7
  8. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/config.ts +100 -0
  9. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/constants.ts +32 -0
  10. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/extension.ts +78 -3
  11. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/hard-deny.ts +16 -4
  12. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/paths.ts +23 -1
  13. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/permissions.ts +22 -1
  14. package/node_modules/@czottmann/pi-automode/extensions/auto-mode/types.ts +18 -2
  15. package/node_modules/@czottmann/pi-automode/package.json +4 -4
  16. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-audit.md +5 -0
  17. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-debt.md +5 -0
  18. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-gain.md +5 -0
  19. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-help.md +5 -0
  20. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail-review.md +5 -0
  21. package/node_modules/@dietrichgebert/ponytail/.opencode/command/ponytail.md +5 -0
  22. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail-frontmatter.cjs +23 -0
  23. package/node_modules/@dietrichgebert/ponytail/.opencode/plugins/ponytail.mjs +99 -0
  24. package/node_modules/@dietrichgebert/ponytail/.qoder/rules/ponytail.md +30 -0
  25. package/node_modules/@dietrichgebert/ponytail/.qoder-plugin/plugin.json +16 -0
  26. package/node_modules/@dietrichgebert/ponytail/AGENTS.md +32 -0
  27. package/node_modules/@dietrichgebert/ponytail/LICENSE +21 -0
  28. package/node_modules/@dietrichgebert/ponytail/README.es.md +298 -0
  29. package/node_modules/@dietrichgebert/ponytail/README.ko.md +315 -0
  30. package/node_modules/@dietrichgebert/ponytail/README.md +351 -0
  31. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-3model.svg +21 -0
  32. package/node_modules/@dietrichgebert/ponytail/assets/benchmark-agentic.svg +62 -0
  33. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.png +0 -0
  34. package/node_modules/@dietrichgebert/ponytail/assets/logo-dark.svg +115 -0
  35. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt-dark.svg +27 -0
  36. package/node_modules/@dietrichgebert/ponytail/assets/logo-greenpt.svg +27 -0
  37. package/node_modules/@dietrichgebert/ponytail/assets/logo.png +0 -0
  38. package/node_modules/@dietrichgebert/ponytail/assets/social-preview.png +0 -0
  39. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-es.png +0 -0
  40. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner-ko.png +0 -0
  41. package/node_modules/@dietrichgebert/ponytail/assets/waitlist-banner.png +0 -0
  42. package/node_modules/@dietrichgebert/ponytail/hooks/claude-codex-hooks.json +44 -0
  43. package/node_modules/@dietrichgebert/ponytail/hooks/copilot-hooks.json +21 -0
  44. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-activate.js +96 -0
  45. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-config.js +169 -0
  46. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-instructions.js +98 -0
  47. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-mode-tracker.js +130 -0
  48. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-runtime.js +85 -0
  49. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.ps1 +24 -0
  50. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-statusline.sh +18 -0
  51. package/node_modules/@dietrichgebert/ponytail/hooks/ponytail-subagent.js +77 -0
  52. package/node_modules/@dietrichgebert/ponytail/hooks/qoder-hooks.json +26 -0
  53. package/node_modules/@dietrichgebert/ponytail/package.json +47 -0
  54. package/node_modules/@dietrichgebert/ponytail/pi-extension/index.js +211 -0
  55. package/node_modules/@dietrichgebert/ponytail/pi-extension/package.json +8 -0
  56. package/node_modules/@dietrichgebert/ponytail/scripts/uninstall.js +60 -0
  57. package/node_modules/@dietrichgebert/ponytail/skills/ponytail/SKILL.md +120 -0
  58. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-audit/SKILL.md +41 -0
  59. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-debt/SKILL.md +44 -0
  60. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-gain/SKILL.md +50 -0
  61. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-help/SKILL.md +71 -0
  62. package/node_modules/@dietrichgebert/ponytail/skills/ponytail-review/SKILL.md +57 -0
  63. package/node_modules/@herbertgao/pi-cc-extensions/README.en.md +1 -3
  64. package/node_modules/@herbertgao/pi-cc-extensions/README.md +1 -3
  65. package/node_modules/@herbertgao/pi-cc-extensions/extensions/config/panel.ts +10 -6
  66. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/agent-summary.ts +6 -2
  67. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/compact-thinking.ts +24 -83
  68. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/context.ts +1 -1
  69. package/node_modules/@herbertgao/pi-cc-extensions/extensions/feature/markdown-enhance.ts +119 -0
  70. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/compact-mode.ts +73 -9
  71. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/index.ts +2 -2
  72. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-hover.ts +130 -0
  73. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-interaction.ts +65 -542
  74. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-layout.ts +160 -0
  75. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-packets.ts +143 -0
  76. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/mouse-scroll.ts +184 -0
  77. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-diff/diff-renderer.ts +2 -2
  78. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/tool-grouping.ts +9 -2
  79. package/node_modules/@herbertgao/pi-cc-extensions/extensions/renderer/transcript-refresh.ts +91 -0
  80. package/node_modules/@herbertgao/pi-cc-extensions/extensions/utils/agent-summary.ts +1 -1
  81. package/node_modules/@herbertgao/pi-cc-extensions/package.json +3 -3
  82. package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-dark.json +1 -0
  83. package/node_modules/@herbertgao/pi-cc-extensions/themes/cc-light.json +2 -2
  84. package/node_modules/@herbertgao/pi-stash/README.md +9 -9
  85. package/node_modules/@herbertgao/pi-stash/package.json +1 -1
  86. package/node_modules/@herbertgao/pi-stash/src/index.ts +2 -1
  87. package/node_modules/@herbertgao/pi-subagents/CHANGELOG.md +14 -0
  88. package/node_modules/@herbertgao/pi-subagents/README.md +6 -2
  89. package/node_modules/@herbertgao/pi-subagents/package.json +3 -3
  90. package/node_modules/@herbertgao/pi-subagents/src/custom-agents.ts +85 -11
  91. package/node_modules/@herbertgao/pi-subagents/src/index.ts +28 -4
  92. package/node_modules/@herbertgao/pi-subagents/src/settings.ts +12 -0
  93. package/node_modules/@herbertgao/pi-subagents/src/types.ts +2 -0
  94. package/node_modules/@narumitw/pi-btw/LICENSE +21 -0
  95. package/node_modules/@narumitw/pi-btw/README.md +210 -0
  96. package/node_modules/@narumitw/pi-btw/package.json +53 -0
  97. package/node_modules/@narumitw/pi-btw/src/bring-to-main.ts +632 -0
  98. package/node_modules/@narumitw/pi-btw/src/btw.ts +996 -0
  99. package/node_modules/@narumitw/pi-btw/src/fullscreen-ui.ts +339 -0
  100. package/node_modules/@narumitw/pi-btw/src/index.ts +1 -0
  101. package/node_modules/@narumitw/pi-btw/src/menu.ts +296 -0
  102. package/node_modules/@narumitw/pi-btw/src/settings.ts +237 -0
  103. package/node_modules/@narumitw/pi-btw/src/side-thread.ts +238 -0
  104. package/node_modules/@narumitw/pi-btw/src/text.ts +10 -0
  105. package/node_modules/@narumitw/pi-btw/src/transcript-pager.ts +620 -0
  106. package/node_modules/@pi-plugins/fast-mode/LICENSE +21 -0
  107. package/node_modules/@pi-plugins/fast-mode/README.md +92 -0
  108. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts +6 -0
  109. package/node_modules/@pi-plugins/fast-mode/dist/index.d.mts.map +1 -0
  110. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs +2026 -0
  111. package/node_modules/@pi-plugins/fast-mode/dist/index.mjs.map +1 -0
  112. package/node_modules/@pi-plugins/fast-mode/package.json +73 -0
  113. package/node_modules/pi-mcp-adapter/CHANGELOG.md +59 -0
  114. package/node_modules/pi-mcp-adapter/README.md +62 -13
  115. package/node_modules/pi-mcp-adapter/agent-plugin-loader.ts +377 -0
  116. package/node_modules/pi-mcp-adapter/commands.ts +9 -7
  117. package/node_modules/pi-mcp-adapter/config.ts +110 -49
  118. package/node_modules/pi-mcp-adapter/direct-tools.ts +34 -19
  119. package/node_modules/pi-mcp-adapter/elicitation-handler.ts +37 -36
  120. package/node_modules/pi-mcp-adapter/errors.ts +21 -17
  121. package/node_modules/pi-mcp-adapter/host-html-template.ts +13 -7
  122. package/node_modules/pi-mcp-adapter/index.ts +37 -14
  123. package/node_modules/pi-mcp-adapter/init.ts +9 -7
  124. package/node_modules/pi-mcp-adapter/json-schema-validator.ts +2 -2
  125. package/node_modules/pi-mcp-adapter/lifecycle.ts +8 -8
  126. package/node_modules/pi-mcp-adapter/logger.ts +1 -1
  127. package/node_modules/pi-mcp-adapter/mcp-auth-flow.ts +36 -22
  128. package/node_modules/pi-mcp-adapter/mcp-auth.ts +120 -2
  129. package/node_modules/pi-mcp-adapter/mcp-code.ts +13 -11
  130. package/node_modules/pi-mcp-adapter/mcp-oauth-provider.ts +19 -13
  131. package/node_modules/pi-mcp-adapter/mcp-output-guard.ts +15 -11
  132. package/node_modules/pi-mcp-adapter/mcp-panel.ts +34 -15
  133. package/node_modules/pi-mcp-adapter/mcp-probe.ts +131 -34
  134. package/node_modules/pi-mcp-adapter/mcp-script-worker.mjs +2 -2
  135. package/node_modules/pi-mcp-adapter/mcp-setup-panel.ts +7 -4
  136. package/node_modules/pi-mcp-adapter/mcp-trace.ts +59 -64
  137. package/node_modules/pi-mcp-adapter/metadata-cache.ts +34 -26
  138. package/node_modules/pi-mcp-adapter/npx-resolver.ts +6 -4
  139. package/node_modules/pi-mcp-adapter/oauth-handler.ts +1 -1
  140. package/node_modules/pi-mcp-adapter/onboarding-state.ts +19 -11
  141. package/node_modules/pi-mcp-adapter/package.json +13 -8
  142. package/node_modules/pi-mcp-adapter/panel-keys.ts +20 -4
  143. package/node_modules/pi-mcp-adapter/prompts.ts +2 -2
  144. package/node_modules/pi-mcp-adapter/proxy-modes.ts +48 -11
  145. package/node_modules/pi-mcp-adapter/sampling-handler.ts +22 -15
  146. package/node_modules/pi-mcp-adapter/search-ranking.ts +2 -1
  147. package/node_modules/pi-mcp-adapter/server-manager.ts +165 -113
  148. package/node_modules/pi-mcp-adapter/session-recovery.ts +11 -12
  149. package/node_modules/pi-mcp-adapter/skills/mcp-scripting/SKILL.md +3 -3
  150. package/node_modules/pi-mcp-adapter/state.ts +3 -1
  151. package/node_modules/pi-mcp-adapter/tool-approval.ts +67 -4
  152. package/node_modules/pi-mcp-adapter/tool-metadata.ts +6 -5
  153. package/node_modules/pi-mcp-adapter/tool-result-renderer.ts +14 -2
  154. package/node_modules/pi-mcp-adapter/ts-shape.ts +9 -2
  155. package/node_modules/pi-mcp-adapter/types.ts +109 -22
  156. package/node_modules/pi-mcp-adapter/ui-app-bridge-helpers.ts +40 -0
  157. package/node_modules/pi-mcp-adapter/ui-resource-handler.ts +28 -19
  158. package/node_modules/pi-mcp-adapter/ui-server.ts +90 -58
  159. package/node_modules/pi-mcp-adapter/ui-session.ts +16 -14
  160. package/node_modules/pi-mcp-adapter/unix-socket-transport.ts +4 -4
  161. package/node_modules/pi-mcp-adapter/utils.ts +23 -9
  162. package/package.json +22 -7
@@ -12,20 +12,20 @@ pi install npm:@herbertgao/pi-stash
12
12
 
13
13
  ## Keybinding
14
14
 
15
- pi-stash overrides Pi's built-in `Ctrl+S` shortcuts. Remap them in `$PI_CODING_AGENT_DIR/keybindings.json` (default: `~/.pi/agent/keybindings.json`), then run `/reload`:
15
+ pi-stash uses `Alt+S`, which does not conflict with Pi's built-in shortcuts.
16
16
 
17
- ```json
18
- {
19
- "app.models.save": ["ctrl+shift+s"],
20
- "app.session.toggleSort": ["ctrl+shift+s"]
21
- }
22
- ```
17
+ ### macOS
18
+
19
+ On macOS, press `Option+S` (`Option` is the `Alt` key). If that inserts `ß` instead of triggering pi-stash, configure the terminal to send `Option` as Meta/Escape:
20
+
21
+ - Terminal.app: **Settings → Profiles → Keyboard → Use Option as Meta key**.
22
+ - iTerm2: **Settings → Profiles → Keys → Left Option key** (or **Right Option key**) → **Esc+**.
23
23
 
24
24
  ## Usage
25
25
 
26
- Press `Ctrl+S` with text in the editor to stash it. Pi clears the editor so you can send another message, then restores the draft as soon as that message is submitted.
26
+ Press `Alt+S` with text in the editor to stash it. Pi clears the editor so you can send another message, then restores the draft as soon as that message is submitted.
27
27
 
28
- Press `Ctrl+S` again while the editor is empty to restore the draft manually. If the editor contains new text, pi-stash keeps both drafts unchanged and refuses to overwrite either one.
28
+ Press `Alt+S` again while the editor is empty to restore the draft manually. If the editor contains new text, pi-stash keeps both drafts unchanged and refuses to overwrite either one.
29
29
 
30
30
  The stash belongs to the current session and survives reloads and restarts. Slash commands and `!` shell commands do not consume it.
31
31
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-stash",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Stash one pi draft and restore it after the next message.",
5
5
  "keywords": [
6
6
  "pi",
@@ -6,6 +6,7 @@ import type {
6
6
  } from "@earendil-works/pi-coding-agent"
7
7
 
8
8
  export const STASH_ENTRY_TYPE = "pi-stash-state"
9
+ export const STASH_SHORTCUT = "alt+s"
9
10
 
10
11
  interface StashState {
11
12
  draft: string | null
@@ -92,7 +93,7 @@ export default function stashExtension(pi: ExtensionAPI) {
92
93
  if (shouldAutoRestore(event.source, event.text)) restoreDraft(ctx)
93
94
  })
94
95
 
95
- pi.registerShortcut("ctrl+s", {
96
+ pi.registerShortcut(STASH_SHORTCUT, {
96
97
  description: "Stash or restore the current draft",
97
98
  handler: (ctx) => {
98
99
  const action = getShortcutAction(ctx.ui.getEditorText(), pendingDraft)
@@ -1,5 +1,11 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.15.2
4
+
5
+ ### Patch Changes
6
+
7
+ - [#15](https://github.com/HerbertGao/pi-extensions/pull/15) [`bc083d2`](https://github.com/HerbertGao/pi-extensions/commit/bc083d27c974f6b5239561dbd2295b6dd53526c0) Thanks [@HerbertGao](https://github.com/HerbertGao)! - Skip unreadable or malformed custom agent files by default, warn when an earlier same-named definition remains active, and add opt-in strict startup validation.
8
+
3
9
  All notable changes to this project will be documented in this file.
4
10
 
5
11
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
@@ -7,6 +13,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
13
 
8
14
  ## [Unreleased]
9
15
 
16
+ ### Added
17
+
18
+ - **`strictAgentFiles` — fail startup on a broken agent file instead of skipping it.** Off by default and applied only during initial extension activation; later per-call reloads remain tolerant.
19
+
20
+ ### Fixed
21
+
22
+ - **One malformed agent file no longer aborts extension activation** ([#212](https://github.com/tintinweb/pi-subagents/issues/212) — thanks [@daromaj](https://github.com/daromaj)). Unreadable and unparseable files are skipped with a path-specific warning, including the earlier source that remains active when a broken file was an override.
23
+
10
24
  ## [0.15.1] - 2026-08-10
11
25
 
12
26
  ### Fixed
@@ -180,6 +180,8 @@ Agents are discovered from three locations (higher priority wins):
180
180
 
181
181
  Project-level agents override global ones with the same name, so you can customize a global agent for a specific project. If both project locations define the same name, **`.pi/agents/` wins** — `.pi` stays the project authority; `.agents/agents/` is an additional read location for projects that keep their agent assets in the `.agents` workspace. The global location follows the upstream `PI_CODING_AGENT_DIR` env var — set it to relocate all pi-coding-agent state (agents, skills, settings) to a custom directory.
182
182
 
183
+ An unreadable or unparseable agent file is skipped by default, with a warning that names the file and error. If the skipped file was overriding a same-named agent, another warning names the earlier file that remains active. Set `strictAgentFiles: true` in `subagents.json` (or `/agents → Settings → Strict agent files`) to fail startup on a broken file instead; mid-session reloads remain tolerant.
184
+
183
185
  ### Example: `.pi/agents/auditor.md`
184
186
 
185
187
  ```markdown
@@ -430,12 +432,14 @@ When on, each subagent spawn's effective model is validated against pi's own `en
430
432
 
431
433
  ## Persistent Settings
432
434
 
433
- Runtime tuning values set via `/agents` → Settings (max concurrency, default max turns, grace turns, nested depth, fallback agent, default join mode, scheduling on/off, scope models on/off, disable defaults on/off, output transcript on/off, tool description full/compact/custom, widget all/background/off) persist across pi restarts. Two files, merged on load:
435
+ Runtime tuning values set via `/agents` → Settings (max concurrency, default max turns, grace turns, nested depth, fallback agent, default join mode, scheduling on/off, scope models on/off, strict agent files on/off, disable defaults on/off, output transcript on/off, tool description full/compact/custom, widget all/background/off) persist across pi restarts. Two files, merged on load:
434
436
 
435
437
  - **Global:** `~/.pi/agent/subagents.json` — your machine-wide defaults. Edit by hand; the `/agents` menu never writes here.
436
438
  - **Project:** `<cwd>/.pi/subagents.json` — per-project overrides. Written by `/agents` → Settings.
437
439
 
438
- **Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (max concurrency `4`, default max turns unlimited, grace turns `5`, nested depth `2`, join mode `smart`, defaults enabled).
440
+ **Precedence:** project overrides global on any field present in both. Missing fields fall back to the hardcoded defaults (max concurrency `4`, default max turns unlimited, grace turns `5`, nested depth `2`, join mode `smart`, strict agent files disabled, defaults enabled).
441
+
442
+ **Strict agent files** (`strictAgentFiles`, default `false`): fail extension startup when any discovered agent file is unreadable or malformed. Enable via `/agents → Settings → Strict agent files` or set `true` in `subagents.json`. Strictness applies only to startup; reloads before later Agent calls remain tolerant so a file edited incorrectly mid-session is skipped with a warning instead of aborting the call.
439
443
 
440
444
  **Nested depth** (`maxSubagentDepth`, default `2`): the hard ceiling on [nested delegation](#nested-subagents), counted from the main session (main = 0, its subagents = 1). `0` or `1` disables nesting project-wide regardless of any agent's `allowed_subagents`. Read when a subagent session is built, so a change applies to agents started after it.
441
445
 
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@herbertgao/pi-subagents",
3
- "version": "0.15.1",
3
+ "version": "0.15.2",
4
4
  "description": "Claude Code-style autonomous subagents for Pi, with HerbertGao-maintained UI extensions.",
5
5
  "keywords": [
6
6
  "agent",
@@ -64,8 +64,8 @@
64
64
  },
65
65
  "x-upstream": {
66
66
  "package": "@tintinweb/pi-subagents",
67
- "version": "0.14.3",
67
+ "version": "0.15.0",
68
68
  "repository": "https://github.com/tintinweb/pi-subagents",
69
- "commit": "2966cd5"
69
+ "commit": "140324c"
70
70
  }
71
71
  }
@@ -8,6 +8,13 @@ import { getAgentDir, parseFrontmatter } from "@earendil-works/pi-coding-agent"
8
8
  import { BUILTIN_TOOL_NAMES } from "./agent-types.js"
9
9
  import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js"
10
10
 
11
+ interface WarningState {
12
+ previous: Set<string>
13
+ current: Set<string>
14
+ }
15
+
16
+ const warningHistoryByCwd = new Map<string, Set<string>>()
17
+
11
18
  /**
12
19
  * Scan for custom agent .md files from multiple locations.
13
20
  * Discovery hierarchy (higher priority wins):
@@ -20,15 +27,36 @@ import type { AgentConfig, MemoryScope, ThinkingLevel } from "./types.js"
20
27
  * authority; .agents/agents is an additional read location.
21
28
  * Any name is allowed — names matching defaults (e.g. "Explore") override them.
22
29
  */
23
- export function loadCustomAgents(cwd: string): Map<string, AgentConfig> {
30
+ export function loadCustomAgents(
31
+ cwd: string,
32
+ strict = false,
33
+ ): Map<string, AgentConfig> {
24
34
  const globalDir = join(getAgentDir(), "agents")
25
35
  const workspaceProjectDir = join(cwd, ".agents", "agents")
26
36
  const projectDir = join(cwd, ".pi", "agents")
27
37
 
28
38
  const agents = new Map<string, AgentConfig>()
29
- loadFromDir(globalDir, agents, "global") // lowest priority
30
- loadFromDir(workspaceProjectDir, agents, "project") // shared workspace
31
- loadFromDir(projectDir, agents, "project") // highest priority (overwrites)
39
+ const skippedOverrides = new Set<string>()
40
+ const warnings: WarningState = {
41
+ previous: warningHistoryByCwd.get(cwd) ?? new Set(),
42
+ current: new Set(),
43
+ }
44
+
45
+ loadFromDir(globalDir, agents, "global", strict, warnings, skippedOverrides) // lowest priority
46
+ loadFromDir(
47
+ workspaceProjectDir,
48
+ agents,
49
+ "project",
50
+ strict,
51
+ warnings,
52
+ skippedOverrides,
53
+ ) // shared workspace
54
+ loadFromDir(projectDir, agents, "project", strict, warnings, skippedOverrides) // highest priority (overwrites)
55
+
56
+ for (const name of skippedOverrides) {
57
+ warnSkippedOverride(name, agents, warnings)
58
+ }
59
+ warningHistoryByCwd.set(cwd, warnings.current)
32
60
  return agents
33
61
  }
34
62
 
@@ -37,6 +65,9 @@ function loadFromDir(
37
65
  dir: string,
38
66
  agents: Map<string, AgentConfig>,
39
67
  source: "project" | "global",
68
+ strict: boolean,
69
+ warnings: WarningState,
70
+ skippedOverrides: Set<string>,
40
71
  ): void {
41
72
  if (!existsSync(dir)) return
42
73
 
@@ -49,16 +80,15 @@ function loadFromDir(
49
80
 
50
81
  for (const file of files) {
51
82
  const name = basename(file, ".md")
83
+ const path = join(dir, file)
52
84
 
53
- let content: string
54
- try {
55
- content = readFileSync(join(dir, file), "utf-8")
56
- } catch {
85
+ const parsed = readAgentFile(path, strict, warnings)
86
+ if (!parsed) {
87
+ skippedOverrides.add(name)
57
88
  continue
58
89
  }
59
-
60
- const { frontmatter: fm, body } =
61
- parseFrontmatter<Record<string, unknown>>(content)
90
+ skippedOverrides.delete(name)
91
+ const { frontmatter: fm, body } = parsed
62
92
 
63
93
  const { builtinToolNames, extSelectors } = parseToolsField(fm.tools)
64
94
 
@@ -97,10 +127,54 @@ function loadFromDir(
97
127
  isolation: fm.isolation === "worktree" ? "worktree" : undefined,
98
128
  enabled: fm.enabled !== false, // default true; explicitly false disables
99
129
  source,
130
+ sourcePath: path,
100
131
  })
101
132
  }
102
133
  }
103
134
 
135
+ /**
136
+ * Read and parse one agent file, or warn and return undefined for the caller to
137
+ * skip. Under strict mode the same failure aborts startup while naming the file.
138
+ */
139
+ function readAgentFile(
140
+ path: string,
141
+ strict: boolean,
142
+ warnings: WarningState,
143
+ ): { frontmatter: Record<string, unknown>; body: string } | undefined {
144
+ try {
145
+ return parseFrontmatter<Record<string, unknown>>(
146
+ readFileSync(path, "utf-8"),
147
+ )
148
+ } catch (err) {
149
+ const reason = err instanceof Error ? err.message : String(err)
150
+ if (strict) throw new Error(`${path}: ${reason}`)
151
+ warnIfNew(`Skipping agent file ${path}: ${reason}`, warnings)
152
+ return undefined
153
+ }
154
+ }
155
+
156
+ /** Warn when a broken higher-priority file exposes an earlier definition. */
157
+ function warnSkippedOverride(
158
+ name: string,
159
+ agents: Map<string, AgentConfig>,
160
+ warnings: WarningState,
161
+ ): void {
162
+ const surviving = agents.get(name)
163
+ if (!surviving?.sourcePath || surviving.enabled === false) return
164
+ warnIfNew(
165
+ `Agent "${name}" now loads from ${surviving.sourcePath} instead`,
166
+ warnings,
167
+ )
168
+ }
169
+
170
+ /** Warn once while an error is unchanged, but report it again after recovery. */
171
+ function warnIfNew(message: string, warnings: WarningState): void {
172
+ if (warnings.current.has(message)) return
173
+ warnings.current.add(message)
174
+ if (warnings.previous.has(message)) return
175
+ console.warn(`[pi-subagents] ${message}`)
176
+ }
177
+
104
178
  // ---- Field parsers ----
105
179
  // All follow the same convention: omitted → default, "none"/empty → nothing, value → exact.
106
180
 
@@ -83,6 +83,7 @@ import { SubagentScheduler } from "./schedule.js"
83
83
  import { resolveStorePath, ScheduleStore } from "./schedule-store.js"
84
84
  import {
85
85
  applyAndEmitLoaded,
86
+ loadSettings,
86
87
  type SubagentsSettings,
87
88
  saveAndEmitChanged,
88
89
  type ToolDescriptionMode,
@@ -426,14 +427,18 @@ export default function (pi: ExtensionAPI) {
426
427
  },
427
428
  )
428
429
 
430
+ // This setting controls the initial load, which runs before the normal settings
431
+ // application below. Later per-call reloads deliberately remain tolerant.
432
+ let strictAgentFiles = loadSettings(process.cwd()).strictAgentFiles === true
433
+
429
434
  /** Reload agents from project/global custom agent dirs and merge with defaults (called on init and each Agent invocation). */
430
- const reloadCustomAgents = () => {
431
- const userAgents = loadCustomAgents(process.cwd())
435
+ const reloadCustomAgents = (strict = false) => {
436
+ const userAgents = loadCustomAgents(process.cwd(), strict)
432
437
  registerAgents(userAgents)
433
438
  }
434
439
 
435
- // Initial load
436
- reloadCustomAgents()
440
+ // Initial load — the only strict one.
441
+ reloadCustomAgents(strictAgentFiles)
437
442
 
438
443
  // ---- Agent activity tracking + widget ----
439
444
  const agentActivity = new Map<string, AgentActivity>()
@@ -990,6 +995,9 @@ export default function (pi: ExtensionAPI) {
990
995
  setDefaultJoinMode,
991
996
  setSchedulingEnabled,
992
997
  setScopeModels: setScopeModelsEnabled,
998
+ setStrictAgentFiles: (enabled) => {
999
+ strictAgentFiles = enabled
1000
+ },
993
1001
  setDisableDefaultAgents: setDisableDefaultAgents,
994
1002
  setToolDescriptionMode: setToolDescriptionMode,
995
1003
  setFleetView: setFleetViewEnabled,
@@ -2673,6 +2681,7 @@ ${systemPrompt}
2673
2681
  defaultJoinMode: getDefaultJoinMode(),
2674
2682
  schedulingEnabled: isSchedulingEnabled(),
2675
2683
  scopeModels: isScopeModelsEnabled(),
2684
+ strictAgentFiles,
2676
2685
  disableDefaultAgents: isDefaultsDisabled(),
2677
2686
  toolDescriptionMode: getToolDescriptionMode(),
2678
2687
  fleetView: isFleetViewEnabled(),
@@ -2762,6 +2771,14 @@ ${systemPrompt}
2762
2771
  currentValue: isScopeModelsEnabled() ? "on" : "off",
2763
2772
  values: ["on", "off"],
2764
2773
  },
2774
+ {
2775
+ id: "strictAgentFiles",
2776
+ label: "Strict agent files",
2777
+ description:
2778
+ "Fail startup on an unreadable or unparseable agent .md instead of skipping it with a warning",
2779
+ currentValue: strictAgentFiles ? "on" : "off",
2780
+ values: ["on", "off"],
2781
+ },
2765
2782
  {
2766
2783
  id: "disableDefaultAgents",
2767
2784
  label: "Disable defaults",
@@ -2867,6 +2884,13 @@ ${systemPrompt}
2867
2884
  const enabled = value === "on"
2868
2885
  setScopeModelsEnabled(enabled)
2869
2886
  notifyApplied(ctx, `Scope models ${enabled ? "enabled" : "disabled"}`)
2887
+ } else if (id === "strictAgentFiles") {
2888
+ const enabled = value === "on"
2889
+ strictAgentFiles = enabled
2890
+ notifyApplied(
2891
+ ctx,
2892
+ `Strict agent files ${enabled ? "enabled" : "disabled"}. Takes effect on next pi session.`,
2893
+ )
2870
2894
  } else if (id === "disableDefaultAgents") {
2871
2895
  const enabled = value === "on"
2872
2896
  setDisableDefaultAgents(enabled)
@@ -49,6 +49,12 @@ export interface SubagentsSettings {
49
49
  * against. Defaults to false: subagents may use any model.
50
50
  */
51
51
  scopeModels?: boolean
52
+ /**
53
+ * When true, an unreadable or unparseable agent `.md` aborts extension load
54
+ * instead of being skipped with a warning. This applies only during startup;
55
+ * later per-call reloads remain tolerant. Defaults to false.
56
+ */
57
+ strictAgentFiles?: boolean
52
58
  /**
53
59
  * When true, the three built-in default agents (general-purpose, Explore, Plan)
54
60
  * are not registered at startup. User-defined agents from project/global custom
@@ -129,6 +135,7 @@ export interface SettingsAppliers {
129
135
  setDefaultJoinMode: (mode: JoinMode) => void
130
136
  setSchedulingEnabled: (b: boolean) => void
131
137
  setScopeModels: (enabled: boolean) => void
138
+ setStrictAgentFiles: (b: boolean) => void
132
139
  setDisableDefaultAgents: (b: boolean) => void
133
140
  setToolDescriptionMode: (mode: ToolDescriptionMode) => void
134
141
  setFleetView: (b: boolean) => void
@@ -207,6 +214,9 @@ function sanitize(raw: unknown): SubagentsSettings {
207
214
  if (typeof r.scopeModels === "boolean") {
208
215
  out.scopeModels = r.scopeModels
209
216
  }
217
+ if (typeof r.strictAgentFiles === "boolean") {
218
+ out.strictAgentFiles = r.strictAgentFiles
219
+ }
210
220
  if (typeof r.disableDefaultAgents === "boolean") {
211
221
  out.disableDefaultAgents = r.disableDefaultAgents
212
222
  }
@@ -315,6 +325,8 @@ export function applySettings(
315
325
  if (typeof s.schedulingEnabled === "boolean")
316
326
  appliers.setSchedulingEnabled(s.schedulingEnabled)
317
327
  if (typeof s.scopeModels === "boolean") appliers.setScopeModels(s.scopeModels)
328
+ if (typeof s.strictAgentFiles === "boolean")
329
+ appliers.setStrictAgentFiles(s.strictAgentFiles)
318
330
  if (typeof s.disableDefaultAgents === "boolean")
319
331
  appliers.setDisableDefaultAgents(s.disableDefaultAgents)
320
332
  if (s.toolDescriptionMode)
@@ -77,6 +77,8 @@ export interface AgentConfig {
77
77
  enabled?: boolean
78
78
  /** Where this agent was loaded from */
79
79
  source?: "default" | "project" | "global"
80
+ /** Path of the .md it was loaded from. Unset for embedded defaults. */
81
+ sourcePath?: string
80
82
  }
81
83
 
82
84
  export type JoinMode = "async" | "group" | "smart"
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 narumiruna
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,210 @@
1
+ # 💬 pi-btw — Side Questions for the Pi Coding Agent
2
+
3
+ [![npm](https://img.shields.io/npm/v/@narumitw/pi-btw)](https://www.npmjs.com/package/@narumitw/pi-btw) [![Pi extension](https://img.shields.io/badge/Pi-extension-blue)](https://pi.dev) [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](./LICENSE)
4
+
5
+ `@narumitw/pi-btw` is a native [Pi coding agent](https://pi.dev) extension that adds `/btw`, a side-question command for quick clarifications that should not interrupt or pollute the main agent conversation.
6
+
7
+ Use it when you want to ask a temporary question, inspect context, or get a short explanation while keeping the primary coding task focused.
8
+
9
+ ## ✨ Features
10
+
11
+ - Adds a `/btw` menu for starting or resuming an in-memory side thread or changing pi-btw settings.
12
+ - Keeps `/btw <question>` as a direct fast path that always starts a fresh side thread.
13
+ - Answers side questions in a dedicated, scrollable full-screen UI.
14
+ - Keeps mouse-drag copying stable while the main agent continues running in the background.
15
+ - Supports follow-up questions in the same ephemeral side thread.
16
+ - Resumes any non-empty side thread retained by the current Pi session, listed by its first question.
17
+ - Queues Pi-style `Steering` questions while an answer is running and processes them one at a time.
18
+ - Optionally brings the latest answer, a question-to-end suffix, an exact line range, or the entire side thread into the main editor.
19
+ - Uses the current session branch as context.
20
+ - Uses Pi's current model or an independent model selected in `pi-btw.json`.
21
+ - Uses a pi-btw thinking level that can be changed with Pi's configured thinking shortcut and remembered for next time.
22
+ - Does not append the side question or answer to the main conversation.
23
+ - Works as an independently installable npm Pi extension package.
24
+
25
+ ## 📦 Install
26
+
27
+ ```bash
28
+ pi install npm:@narumitw/pi-btw
29
+ ```
30
+
31
+ Try without installing permanently:
32
+
33
+ ```bash
34
+ pi -e npm:@narumitw/pi-btw
35
+ ```
36
+
37
+ Try this package locally from the repository root:
38
+
39
+ ```bash
40
+ pi -e ./packages/pi-btw
41
+ ```
42
+
43
+ ## 🚀 Usage
44
+
45
+ Open the pi-btw menu or provide the first question immediately:
46
+
47
+ ```text
48
+ /btw
49
+ /btw <your side question>
50
+ ```
51
+
52
+ Examples:
53
+
54
+ ```text
55
+ /btw
56
+ /btw what does this TypeScript error mean?
57
+ /btw summarize the current implementation before we continue
58
+ /btw is this API name idiomatic?
59
+ ```
60
+
61
+ Running `/btw` alone opens a menu with **Start side thread** selected first. When the current Pi
62
+ session has non-empty side threads in memory, **Resume side thread** opens a bounded choice list;
63
+ **Settings** changes the starting thinking level and whether shortcut changes are remembered.
64
+ Each Resume row keeps the first question as its fixed title, shows its question count, and the list
65
+ is ordered by the newest recorded answer or visible error. Opening and closing a thread without a
66
+ new result does not reorder it. `/btw <question>` bypasses this menu and always starts a fresh side
67
+ thread. Its answer opens above the side-thread editor. The side thread uses a dedicated full-screen
68
+ terminal view.
69
+ The main agent continues running in the background, but its screen rendering stays suspended until
70
+ `/btw` closes, so new main-thread output cannot move a mouse selection inside the side thread.
71
+ Drag the primary mouse button across side-thread text to select and copy it through Pi's terminal
72
+ clipboard support. Returning from `/btw` redraws the main view with everything produced while it
73
+ was hidden. A compact `btw · side thread` header stays fixed above the content so the ephemeral
74
+ workspace remains recognizable while scrolling. Messages use Pi's normal
75
+ user and assistant presentation without numbered turns or role labels. Type each question and press
76
+ `Enter`; no follow-up shortcut is required.
77
+ Previous side questions and answers remain available to the model and visible for that
78
+ invocation. The side-thread header shows its current thinking level. Press Pi's configured
79
+ `app.thinking.cycle` shortcut (`Shift+Tab` by default) in the composer to cycle the levels
80
+ supported by the side-thread model; every later question uses the displayed level until it is
81
+ changed again. By default, each shortcut change is also written to `pi-btw.json` for the next
82
+ invocation. Turn **Remember thinking level changes** off in Settings to keep changes local to the
83
+ current side thread. Neither path changes the main session's thinking level.
84
+ While a response is running, the transcript and composer remain visible above an `Answering…`
85
+ status.
86
+ Type another question and press `Enter` to queue it as `Steering`; queued questions are shown in
87
+ submission order and answered one at a time after the active response completes.
88
+ A queued question uses the side thread's thinking level when its turn begins.
89
+ A failed active response is shown in the transcript and does not discard later steering questions.
90
+ The footer shows `PgUp`/`PgDn` only when history can scroll; press `Ctrl+C` to cancel the active
91
+ response and discard the ephemeral side-thread draft and steering queue. Completed questions,
92
+ answers, and visible errors remain available through Resume until the current extension instance
93
+ ends. Steering remains entirely inside pi-btw and never appends to the main conversation or editor.
94
+
95
+ After at least one successful answer, press `Ctrl+R` to bring selected context to the main
96
+ editor. The scope menu shows the size of the latest question and answer and the entire side
97
+ thread before you choose. Bring the latest question and answer, everything from a chosen
98
+ question onward, an exact text range, or the entire side thread. Question-suffix, exact-range,
99
+ and entire-thread choices preview the exact editable context block before the side thread closes;
100
+ `Escape` returns and `Ctrl+C` closes without bringing anything to main.
101
+
102
+ The text-range selector supports both fast line selection and editor-style character selection.
103
+ It reports whether anything is selected plus the selected line, message, and approximate token
104
+ counts. Press `Space` to select the current raw source line, then use `Up`/`Down` to extend by
105
+ whole lines; press `Space` again to clear it. Alternatively, use the arrow keys to move the cursor
106
+ and `Shift`+arrow keys to extend a character-level selection. Starting a Shift selection replaces
107
+ any active line selection. Selected lines include a visible `●` marker in addition to highlighting.
108
+ Pi's configured keys control vertical navigation, bringing, and going back (`Up`/`Down`, `Enter`,
109
+ and `Escape` by default), and the selector displays the active keys. Selection follows raw source
110
+ text rather than terminal-wrapped visual rows.
111
+
112
+ Bringing context to main closes the side thread and loads a deterministic, editable context block
113
+ into Pi's main editor. It never sends the draft automatically. If the main editor already has a
114
+ draft, append is the recommended default. Replace is labeled as destructive and requires a second
115
+ confirmation; Cancel returns to the side thread without changing either draft. Concurrent editor
116
+ updates made while these menus are open are preserved. A success message reports whether context
117
+ was loaded, appended, or replaced and its approximate size.
118
+ Without an explicit bring-to-main action, closing `/btw` never adds the side thread to the main
119
+ conversation. Non-empty threads remain only in memory for Resume within the current Pi session.
120
+ `/new`, Pi `/resume`, `/reload`, extension replacement, and process restart discard every retained
121
+ thread. Unsent drafts, steering queues, interrupted answers, and model credentials are never retained.
122
+
123
+ ## ⚙️ Model and thinking level
124
+
125
+ By default, `/btw` uses the current session model. To use an independent model for side
126
+ questions, create:
127
+
128
+ ```text
129
+ $PI_CODING_AGENT_DIR/pi-btw.json
130
+ ```
131
+
132
+ The normal location is `~/.pi/agent/pi-btw.json`. `PI_CODING_AGENT_DIR` is an existing Pi
133
+ setting; pi-btw does not add any environment variables.
134
+
135
+ ```json
136
+ {
137
+ "model": "anthropic/claude-sonnet-4-5",
138
+ "thinkingLevel": "low",
139
+ "rememberThinkingLevelChanges": true
140
+ }
141
+ ```
142
+
143
+ The `model` value uses `provider/model-id` format. Only the first `/` is the separator, so
144
+ model IDs may contain additional slashes, such as `openrouter/anthropic/claude-sonnet`.
145
+ The configured model must exist in Pi's model registry and have usable credentials. If it
146
+ cannot be found or authenticated, pi-btw warns and falls back to the current session model.
147
+ If neither model is available, `/btw` reports an error and stops. This selection affects only
148
+ `/btw`; it does not change the main session model.
149
+
150
+ Pi calls its reasoning setting the **thinking level**. `thinkingLevel` sets pi-btw's starting
151
+ level; accepted values are `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, and `max`. When the
152
+ field is absent for backward compatibility, the next invocation starts from the current session
153
+ level. The initial value and shortcut cycle are clamped to the selected side model's capabilities
154
+ using Pi's model rules. Pi-btw does not read, write, or change the main session's
155
+ `defaultThinkingLevel`.
156
+
157
+ `rememberThinkingLevelChanges` controls only persistence and defaults to `true` when omitted. A
158
+ side-thread shortcut always changes that side thread immediately. When remembering is on, the
159
+ concrete level is written for the next invocation; when off, `pi-btw.json` stays unchanged. If a
160
+ shortcut write fails, the local change remains active and pi-btw warns that it was not remembered.
161
+ A failed Settings-screen save instead restores the previous displayed value.
162
+
163
+ A missing settings file is a side-effect-free read: pi-btw creates it only after a Settings change
164
+ or a remembered shortcut change. Saves are ordered within the Pi process and published atomically
165
+ with a same-directory temporary file and rename. They preserve `model` and unknown fields; malformed
166
+ or invalid files block saves and remain unchanged. Settings must be valid UTF-8 and no larger than
167
+ 64 KiB, so unexpectedly large or invalidly encoded files are rejected without being rewritten.
168
+ Separate Pi processes and external editors are outside this in-process ordering boundary. The file
169
+ is read for each `/btw` invocation, so edits apply without `/reload`.
170
+
171
+ ## 🧠 Why use pi-btw?
172
+
173
+ Normal assistant messages become part of the main Pi conversation and can distract the coding agent from the task. `pi-btw` creates a lightweight side channel for context-aware questions, making it useful for pair programming, debugging, code review, and repository exploration.
174
+
175
+ ## 🗂️ Package layout
176
+
177
+ ```txt
178
+ packages/pi-btw/
179
+ ├── src/
180
+ │ ├── index.ts
181
+ │ ├── btw.ts
182
+ │ ├── bring-to-main.ts
183
+ │ ├── menu.ts
184
+ │ ├── settings.ts
185
+ │ ├── side-thread.ts
186
+ │ ├── text.ts
187
+ │ └── transcript-pager.ts
188
+ ├── README.md
189
+ ├── LICENSE
190
+ ├── tsconfig.json
191
+ └── package.json
192
+ ```
193
+
194
+ The package exposes its Pi extension through `package.json`:
195
+
196
+ ```json
197
+ {
198
+ "pi": {
199
+ "extensions": ["./src/index.ts"]
200
+ }
201
+ }
202
+ ```
203
+
204
+ ## 🔎 Keywords
205
+
206
+ Pi extension, Pi coding agent, AI coding agent, side question command, agent chat workflow, TypeScript Pi package, npm Pi extension.
207
+
208
+ ## 📄 License
209
+
210
+ MIT. See [`LICENSE`](./LICENSE).