@fleetagent/pi-coding-agent 0.0.5 → 0.0.7

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 (201) hide show
  1. package/CHANGELOG.md +49 -0
  2. package/README.md +28 -5
  3. package/dist/cli/args.d.ts +2 -0
  4. package/dist/cli/args.d.ts.map +1 -1
  5. package/dist/cli/args.js +9 -0
  6. package/dist/cli/args.js.map +1 -1
  7. package/dist/cli/file-processor.d.ts.map +1 -1
  8. package/dist/cli/file-processor.js +2 -3
  9. package/dist/cli/file-processor.js.map +1 -1
  10. package/dist/config.d.ts.map +1 -1
  11. package/dist/config.js +15 -2
  12. package/dist/config.js.map +1 -1
  13. package/dist/core/agent-session.d.ts +13 -3
  14. package/dist/core/agent-session.d.ts.map +1 -1
  15. package/dist/core/agent-session.js +130 -23
  16. package/dist/core/agent-session.js.map +1 -1
  17. package/dist/core/diagnostics.d.ts +1 -1
  18. package/dist/core/diagnostics.d.ts.map +1 -1
  19. package/dist/core/diagnostics.js.map +1 -1
  20. package/dist/core/export-html/template.js +6 -3
  21. package/dist/core/extensions/runner.d.ts +5 -1
  22. package/dist/core/extensions/runner.d.ts.map +1 -1
  23. package/dist/core/extensions/runner.js +13 -3
  24. package/dist/core/extensions/runner.js.map +1 -1
  25. package/dist/core/extensions/types.d.ts +6 -3
  26. package/dist/core/extensions/types.d.ts.map +1 -1
  27. package/dist/core/extensions/types.js.map +1 -1
  28. package/dist/core/model-registry.d.ts.map +1 -1
  29. package/dist/core/model-registry.js +65 -13
  30. package/dist/core/model-registry.js.map +1 -1
  31. package/dist/core/output-guard.d.ts +1 -0
  32. package/dist/core/output-guard.d.ts.map +1 -1
  33. package/dist/core/output-guard.js +52 -22
  34. package/dist/core/output-guard.js.map +1 -1
  35. package/dist/core/package-manager.d.ts +1 -0
  36. package/dist/core/package-manager.d.ts.map +1 -1
  37. package/dist/core/package-manager.js +161 -24
  38. package/dist/core/package-manager.js.map +1 -1
  39. package/dist/core/pi-agent.d.ts.map +1 -1
  40. package/dist/core/pi-agent.js +12 -3
  41. package/dist/core/pi-agent.js.map +1 -1
  42. package/dist/core/resolve-config-value.d.ts +9 -1
  43. package/dist/core/resolve-config-value.d.ts.map +1 -1
  44. package/dist/core/resolve-config-value.js +134 -11
  45. package/dist/core/resolve-config-value.js.map +1 -1
  46. package/dist/core/resource-loader.d.ts +30 -0
  47. package/dist/core/resource-loader.d.ts.map +1 -1
  48. package/dist/core/resource-loader.js +94 -0
  49. package/dist/core/resource-loader.js.map +1 -1
  50. package/dist/core/rules.d.ts +57 -0
  51. package/dist/core/rules.d.ts.map +1 -0
  52. package/dist/core/rules.js +384 -0
  53. package/dist/core/rules.js.map +1 -0
  54. package/dist/core/session/jsonl-helpers.d.ts +2 -1
  55. package/dist/core/session/jsonl-helpers.d.ts.map +1 -1
  56. package/dist/core/session/jsonl-helpers.js +6 -3
  57. package/dist/core/session/jsonl-helpers.js.map +1 -1
  58. package/dist/core/session/local-session-manager.d.ts +1 -0
  59. package/dist/core/session/local-session-manager.d.ts.map +1 -1
  60. package/dist/core/session/local-session-manager.js +12 -4
  61. package/dist/core/session/local-session-manager.js.map +1 -1
  62. package/dist/core/session/session-manager.d.ts +1 -0
  63. package/dist/core/session/session-manager.d.ts.map +1 -1
  64. package/dist/core/session/session-manager.js.map +1 -1
  65. package/dist/core/session/stores/jsonl-session-store.d.ts +2 -1
  66. package/dist/core/session/stores/jsonl-session-store.d.ts.map +1 -1
  67. package/dist/core/session/stores/jsonl-session-store.js +105 -78
  68. package/dist/core/session/stores/jsonl-session-store.js.map +1 -1
  69. package/dist/core/settings-manager.d.ts +7 -0
  70. package/dist/core/settings-manager.d.ts.map +1 -1
  71. package/dist/core/settings-manager.js +28 -9
  72. package/dist/core/settings-manager.js.map +1 -1
  73. package/dist/core/slash-commands.d.ts +1 -1
  74. package/dist/core/slash-commands.d.ts.map +1 -1
  75. package/dist/core/slash-commands.js +1 -1
  76. package/dist/core/slash-commands.js.map +1 -1
  77. package/dist/core/system-prompt.d.ts +3 -0
  78. package/dist/core/system-prompt.d.ts.map +1 -1
  79. package/dist/core/system-prompt.js +11 -3
  80. package/dist/core/system-prompt.js.map +1 -1
  81. package/dist/core/tools/bash.d.ts.map +1 -1
  82. package/dist/core/tools/bash.js +73 -63
  83. package/dist/core/tools/bash.js.map +1 -1
  84. package/dist/core/tools/edit.d.ts.map +1 -1
  85. package/dist/core/tools/edit.js +45 -76
  86. package/dist/core/tools/edit.js.map +1 -1
  87. package/dist/core/tools/file-mutation-queue.d.ts.map +1 -1
  88. package/dist/core/tools/file-mutation-queue.js +27 -12
  89. package/dist/core/tools/file-mutation-queue.js.map +1 -1
  90. package/dist/core/tools/find.d.ts.map +1 -1
  91. package/dist/core/tools/find.js +11 -2
  92. package/dist/core/tools/find.js.map +1 -1
  93. package/dist/core/tools/grep.d.ts.map +1 -1
  94. package/dist/core/tools/grep.js +3 -3
  95. package/dist/core/tools/grep.js.map +1 -1
  96. package/dist/core/tools/ls.d.ts.map +1 -1
  97. package/dist/core/tools/ls.js +13 -4
  98. package/dist/core/tools/ls.js.map +1 -1
  99. package/dist/core/tools/path-utils.d.ts +1 -0
  100. package/dist/core/tools/path-utils.d.ts.map +1 -1
  101. package/dist/core/tools/path-utils.js +37 -0
  102. package/dist/core/tools/path-utils.js.map +1 -1
  103. package/dist/core/tools/read.d.ts.map +1 -1
  104. package/dist/core/tools/read.js +13 -8
  105. package/dist/core/tools/read.js.map +1 -1
  106. package/dist/core/tools/write.d.ts.map +1 -1
  107. package/dist/core/tools/write.js +24 -32
  108. package/dist/core/tools/write.js.map +1 -1
  109. package/dist/index.d.ts +1 -0
  110. package/dist/index.d.ts.map +1 -1
  111. package/dist/index.js +2 -0
  112. package/dist/index.js.map +1 -1
  113. package/dist/main.d.ts.map +1 -1
  114. package/dist/main.js +6 -2
  115. package/dist/main.js.map +1 -1
  116. package/dist/migrations.d.ts.map +1 -1
  117. package/dist/migrations.js +118 -1
  118. package/dist/migrations.js.map +1 -1
  119. package/dist/modes/interactive/components/config-selector.d.ts +1 -1
  120. package/dist/modes/interactive/components/config-selector.d.ts.map +1 -1
  121. package/dist/modes/interactive/components/config-selector.js +12 -3
  122. package/dist/modes/interactive/components/config-selector.js.map +1 -1
  123. package/dist/modes/interactive/components/footer.d.ts +1 -0
  124. package/dist/modes/interactive/components/footer.d.ts.map +1 -1
  125. package/dist/modes/interactive/components/footer.js +14 -5
  126. package/dist/modes/interactive/components/footer.js.map +1 -1
  127. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  128. package/dist/modes/interactive/components/settings-selector.js +1 -1
  129. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  130. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  131. package/dist/modes/interactive/components/user-message.js +1 -1
  132. package/dist/modes/interactive/components/user-message.js.map +1 -1
  133. package/dist/modes/interactive/interactive-mode.d.ts +1 -0
  134. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  135. package/dist/modes/interactive/interactive-mode.js +64 -9
  136. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  137. package/dist/modes/interactive/theme/theme.d.ts.map +1 -1
  138. package/dist/modes/interactive/theme/theme.js +10 -0
  139. package/dist/modes/interactive/theme/theme.js.map +1 -1
  140. package/dist/modes/rpc/rpc-client.d.ts +5 -0
  141. package/dist/modes/rpc/rpc-client.d.ts.map +1 -1
  142. package/dist/modes/rpc/rpc-client.js +91 -18
  143. package/dist/modes/rpc/rpc-client.js.map +1 -1
  144. package/dist/modes/rpc/rpc-mode.d.ts.map +1 -1
  145. package/dist/modes/rpc/rpc-mode.js +23 -3
  146. package/dist/modes/rpc/rpc-mode.js.map +1 -1
  147. package/dist/modes/rpc/rpc-types.d.ts +1 -1
  148. package/dist/modes/rpc/rpc-types.d.ts.map +1 -1
  149. package/dist/modes/rpc/rpc-types.js.map +1 -1
  150. package/dist/utils/clipboard-native.d.ts +3 -1
  151. package/dist/utils/clipboard-native.d.ts.map +1 -1
  152. package/dist/utils/clipboard-native.js +14 -8
  153. package/dist/utils/clipboard-native.js.map +1 -1
  154. package/dist/utils/deprecation.d.ts +4 -0
  155. package/dist/utils/deprecation.d.ts.map +1 -0
  156. package/dist/utils/deprecation.js +13 -0
  157. package/dist/utils/deprecation.js.map +1 -0
  158. package/dist/utils/image-resize-core.d.ts +30 -0
  159. package/dist/utils/image-resize-core.d.ts.map +1 -0
  160. package/dist/utils/image-resize-core.js +124 -0
  161. package/dist/utils/image-resize-core.js.map +1 -0
  162. package/dist/utils/image-resize-worker.d.ts +2 -0
  163. package/dist/utils/image-resize-worker.d.ts.map +1 -0
  164. package/dist/utils/image-resize-worker.js +31 -0
  165. package/dist/utils/image-resize-worker.js.map +1 -0
  166. package/dist/utils/image-resize.d.ts +6 -27
  167. package/dist/utils/image-resize.d.ts.map +1 -1
  168. package/dist/utils/image-resize.js +60 -116
  169. package/dist/utils/image-resize.js.map +1 -1
  170. package/dist/utils/json.d.ts +3 -0
  171. package/dist/utils/json.d.ts.map +1 -0
  172. package/dist/utils/json.js +7 -0
  173. package/dist/utils/json.js.map +1 -0
  174. package/docs/custom-provider.md +22 -9
  175. package/docs/extensions.md +13 -11
  176. package/docs/index.md +3 -2
  177. package/docs/models.md +34 -12
  178. package/docs/packages.md +11 -8
  179. package/docs/providers.md +13 -5
  180. package/docs/quickstart.md +1 -1
  181. package/docs/rpc.md +4 -2
  182. package/docs/rules.md +102 -0
  183. package/docs/sdk.md +57 -1
  184. package/docs/settings.md +6 -3
  185. package/docs/terminal-setup.md +6 -0
  186. package/docs/usage.md +6 -4
  187. package/examples/extensions/README.md +2 -1
  188. package/examples/extensions/custom-provider-anthropic/index.ts +1 -1
  189. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  190. package/examples/extensions/custom-provider-gitlab-duo/index.ts +54 -3
  191. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  192. package/examples/extensions/dynamic-resources/RULES.md +8 -0
  193. package/examples/extensions/dynamic-resources/index.ts +1 -0
  194. package/examples/extensions/git-merge-and-resolve.ts +115 -0
  195. package/examples/extensions/reload-runtime.ts +2 -2
  196. package/examples/extensions/sandbox/package.json +1 -1
  197. package/examples/extensions/with-deps/package.json +1 -1
  198. package/examples/sdk/12-full-control.ts +1 -0
  199. package/examples/sdk/README.md +1 -1
  200. package/npm-shrinkwrap.json +13 -12
  201. package/package.json +5 -5
@@ -43,7 +43,7 @@ export default function (pi: ExtensionAPI) {
43
43
  pi.registerProvider("my-provider", {
44
44
  name: "My Provider",
45
45
  baseUrl: "https://api.example.com",
46
- apiKey: "MY_API_KEY",
46
+ apiKey: "$MY_API_KEY",
47
47
  api: "openai-completions",
48
48
  models: [
49
49
  {
@@ -83,7 +83,7 @@ pi.registerProvider("openai", {
83
83
  pi.registerProvider("google", {
84
84
  baseUrl: "https://ai-gateway.corp.com/google",
85
85
  headers: {
86
- "X-Corp-Auth": "CORP_AUTH_TOKEN" // env var or literal
86
+ "X-Corp-Auth": "$CORP_AUTH_TOKEN" // env var or literal
87
87
  }
88
88
  });
89
89
  ```
@@ -112,7 +112,7 @@ export default async function (pi: ExtensionAPI) {
112
112
 
113
113
  pi.registerProvider("local-openai", {
114
114
  baseUrl: "http://localhost:1234/v1",
115
- apiKey: "LOCAL_OPENAI_API_KEY",
115
+ apiKey: "$LOCAL_OPENAI_API_KEY",
116
116
  api: "openai-completions",
117
117
  models: payload.data.map((model) => ({
118
118
  id: model.id,
@@ -132,7 +132,7 @@ This registers the fetched models before startup finishes.
132
132
  ```typescript
133
133
  pi.registerProvider("my-llm", {
134
134
  baseUrl: "https://api.my-llm.com/v1",
135
- apiKey: "MY_LLM_API_KEY", // env var name or literal value
135
+ apiKey: "$MY_LLM_API_KEY", // env var reference
136
136
  api: "openai-completions", // which streaming API to use
137
137
  models: [
138
138
  {
@@ -155,6 +155,8 @@ pi.registerProvider("my-llm", {
155
155
 
156
156
  When `models` is provided, it **replaces** all existing models for that provider.
157
157
 
158
+ `apiKey` and custom header values use the same config value syntax as `models.json`: `!command` at the start executes a command for the whole value, `$ENV_VAR` and `${ENV_VAR}` interpolate environment variables, `$$` emits a literal `$`, and `$!` emits a literal `!`.
159
+
158
160
  ## Unregister Provider
159
161
 
160
162
  Use `pi.unregisterProvider(name)` to remove a provider that was previously registered via `pi.registerProvider(name, ...)`:
@@ -163,7 +165,7 @@ Use `pi.unregisterProvider(name)` to remove a provider that was previously regis
163
165
  // Register
164
166
  pi.registerProvider("my-llm", {
165
167
  baseUrl: "https://api.my-llm.com/v1",
166
- apiKey: "MY_LLM_API_KEY",
168
+ apiKey: "$MY_LLM_API_KEY",
167
169
  api: "openai-completions",
168
170
  models: [
169
171
  {
@@ -230,6 +232,9 @@ models: [{
230
232
  Use `openrouter` for OpenRouter-style `reasoning: { effort }` controls. Use `together` for Together-style `reasoning: { enabled }` controls; with `supportsReasoningEffort`, it also sends `reasoning_effort`. Use `qwen-chat-template` instead for local Qwen-compatible servers that read `chat_template_kwargs.enable_thinking`.
231
233
  Use `cacheControlFormat: "anthropic"` for OpenAI-compatible providers that expose Anthropic-style prompt caching via `cache_control` on the system prompt, last tool definition, and last user/assistant text content.
232
234
 
235
+ For Anthropic-compatible providers using `api: "anthropic-messages"`, set `compat.forceAdaptiveThinking: true` on models or providers whose upstream model requires adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`). Built-in adaptive Claude models set this automatically. Set `compat.allowEmptySignature: true` only for providers that emit empty thinking signatures and expect `signature: ""` on replay.
236
+
237
+
233
238
  > Migration note: Mistral moved from `openai-completions` to `mistral-conversations`.
234
239
  > Use `mistral-conversations` for native Mistral models.
235
240
  > If you intentionally route Mistral-compatible/custom endpoints through `openai-completions`, set `compat` flags explicitly as needed.
@@ -241,7 +246,7 @@ If your provider expects `Authorization: Bearer <key>` but doesn't use a standar
241
246
  ```typescript
242
247
  pi.registerProvider("custom-api", {
243
248
  baseUrl: "https://api.example.com",
244
- apiKey: "MY_API_KEY",
249
+ apiKey: "$MY_API_KEY",
245
250
  authHeader: true, // adds Authorization: Bearer header
246
251
  api: "openai-completions",
247
252
  models: [...]
@@ -568,7 +573,7 @@ Register your stream function:
568
573
  ```typescript
569
574
  pi.registerProvider("my-provider", {
570
575
  baseUrl: "https://api.example.com",
571
- apiKey: "MY_API_KEY",
576
+ apiKey: "$MY_API_KEY",
572
577
  api: "my-custom-api",
573
578
  models: [...],
574
579
  streamSimple: streamMyProvider
@@ -605,7 +610,7 @@ interface ProviderConfig {
605
610
  /** API endpoint URL. Required when defining models. */
606
611
  baseUrl?: string;
607
612
 
608
- /** API key or environment variable name. Required when defining models (unless oauth). */
613
+ /** API key literal, env interpolation ($ENV_VAR or ${ENV_VAR}), or !command. Required when defining models (unless oauth). */
609
614
  apiKey?: string;
610
615
 
611
616
  /** API type for streaming. Required at provider or model level when defining models. */
@@ -618,7 +623,7 @@ interface ProviderConfig {
618
623
  options?: SimpleStreamOptions
619
624
  ) => AssistantMessageEventStream;
620
625
 
621
- /** Custom headers to include in requests. Values can be env var names. */
626
+ /** Custom headers to include in requests. Values use the same resolution syntax as apiKey. */
622
627
  headers?: Record<string, string>;
623
628
 
624
629
  /** If true, adds Authorization: Bearer header with the resolved API key. */
@@ -693,6 +698,14 @@ interface ProviderModelConfig {
693
698
  requiresReasoningContentOnAssistantMessages?: boolean;
694
699
  thinkingFormat?: "openai" | "openrouter" | "deepseek" | "together" | "zai" | "qwen" | "qwen-chat-template";
695
700
  cacheControlFormat?: "anthropic";
701
+ // anthropic-messages
702
+ supportsEagerToolInputStreaming?: boolean;
703
+ supportsLongCacheRetention?: boolean;
704
+ sendSessionAffinityHeaders?: boolean;
705
+ supportsCacheControlOnTools?: boolean;
706
+ forceAdaptiveThinking?: boolean;
707
+ allowEmptySignature?: boolean;
708
+
696
709
  };
697
710
  }
698
711
  ```
@@ -199,7 +199,7 @@ export default async function (pi: ExtensionAPI) {
199
199
 
200
200
  pi.registerProvider("local-openai", {
201
201
  baseUrl: "http://localhost:1234/v1",
202
- apiKey: "LOCAL_OPENAI_API_KEY",
202
+ apiKey: "$LOCAL_OPENAI_API_KEY",
203
203
  api: "openai-completions",
204
204
  models: payload.data.map((model) => ({
205
205
  id: model.id,
@@ -338,7 +338,7 @@ exit (Ctrl+C, Ctrl+D, SIGHUP, SIGTERM)
338
338
 
339
339
  #### resources_discover
340
340
 
341
- Fired after `session_start` so extensions can contribute additional skill, prompt, and theme paths.
341
+ Fired after `session_start` so extensions can contribute additional skill, rule, prompt, and theme paths.
342
342
  The startup path uses `reason: "startup"`. Reload uses `reason: "reload"`.
343
343
 
344
344
  ```typescript
@@ -347,6 +347,7 @@ pi.on("resources_discover", async (event, _ctx) => {
347
347
  // event.reason - "startup" | "reload"
348
348
  return {
349
349
  skillPaths: ["/path/to/skills"],
350
+ rulePaths: ["/path/to/rules"],
350
351
  promptPaths: ["/path/to/prompts"],
351
352
  themePaths: ["/path/to/themes"],
352
353
  };
@@ -813,7 +814,7 @@ Fired when user input is received, after extension commands are checked but befo
813
814
  **Processing order:**
814
815
  1. Extension commands (`/cmd`) checked first - if found, handler runs and input event is skipped
815
816
  2. `input` event fires - can intercept, transform, or handle
816
- 3. If not handled: skill commands (`/skill:name`) expanded to skill content
817
+ 3. If not handled: skill/rule commands (`/skill:name`, `/rule:name`) expanded to their content
817
818
  4. If not handled: prompt templates (`/template`) expanded to template content
818
819
  5. Agent processing begins (`before_agent_start`, etc.)
819
820
 
@@ -836,8 +837,8 @@ pi.on("input", async (event, ctx) => {
836
837
  // Route by source: skip processing for extension-injected messages
837
838
  if (event.source === "extension") return { action: "continue" };
838
839
 
839
- // Intercept skill commands before expansion
840
- if (event.text.startsWith("/skill:")) {
840
+ // Intercept skill/rule commands before expansion
841
+ if (event.text.startsWith("/skill:") || event.text.startsWith("/rule:")) {
841
842
  // Could transform, block, or let pass through
842
843
  }
843
844
 
@@ -1161,7 +1162,7 @@ Run the same reload flow as `/reload`.
1161
1162
 
1162
1163
  ```typescript
1163
1164
  pi.registerCommand("reload-runtime", {
1164
- description: "Reload extensions, skills, prompts, and themes",
1165
+ description: "Reload extensions, skills, rules, prompts, and themes",
1165
1166
  handler: async (_args, ctx) => {
1166
1167
  await ctx.reload();
1167
1168
  return;
@@ -1189,7 +1190,7 @@ import { Type } from "typebox";
1189
1190
 
1190
1191
  export default function (pi: ExtensionAPI) {
1191
1192
  pi.registerCommand("reload-runtime", {
1192
- description: "Reload extensions, skills, prompts, and themes",
1193
+ description: "Reload extensions, skills, rules, prompts, and themes",
1193
1194
  handler: async (_args, ctx) => {
1194
1195
  await ctx.reload();
1195
1196
  return;
@@ -1199,7 +1200,7 @@ export default function (pi: ExtensionAPI) {
1199
1200
  pi.registerTool({
1200
1201
  name: "reload_runtime",
1201
1202
  label: "Reload Runtime",
1202
- description: "Reload extensions, skills, prompts, and themes",
1203
+ description: "Reload extensions, skills, rules, prompts, and themes",
1203
1204
  parameters: Type.Object({}),
1204
1205
  async execute() {
1205
1206
  pi.sendUserMessage("/reload-runtime", { deliverAs: "followUp" });
@@ -1409,7 +1410,7 @@ pi.registerCommand("deploy", {
1409
1410
 
1410
1411
  ### pi.getCommands()
1411
1412
 
1412
- Get the slash commands available for invocation via `prompt` in the current session. Includes extension commands, prompt templates, and skill commands.
1413
+ Get the slash commands available for invocation via `prompt` in the current session. Includes extension commands, prompt templates, skill commands, and rule commands.
1413
1414
  The list matches the RPC `get_commands` ordering: extensions first, then templates, then skills.
1414
1415
 
1415
1416
  ```typescript
@@ -1554,7 +1555,7 @@ If you need to discover models from a remote endpoint, prefer an async extension
1554
1555
  pi.registerProvider("my-proxy", {
1555
1556
  name: "My Proxy",
1556
1557
  baseUrl: "https://proxy.example.com",
1557
- apiKey: "PROXY_API_KEY", // env var name or literal
1558
+ apiKey: "$PROXY_API_KEY", // env var reference
1558
1559
  api: "anthropic-messages",
1559
1560
  models: [
1560
1561
  {
@@ -1601,7 +1602,7 @@ pi.registerProvider("corporate-ai", {
1601
1602
  **Config options:**
1602
1603
  - `name` - Display name for the provider in UI such as `/login`.
1603
1604
  - `baseUrl` - API endpoint URL. Required when defining models.
1604
- - `apiKey` - API key or environment variable name. Required when defining models (unless `oauth` provided).
1605
+ - `apiKey` - API key literal, environment interpolation (`$ENV_VAR` or `${ENV_VAR}`), or leading `!command`. Required when defining models (unless `oauth` provided). `$$` escapes `$`, and `$!` escapes a literal `!` without triggering command execution.
1605
1606
  - `api` - API type: `"anthropic-messages"`, `"openai-completions"`, `"openai-responses"`, etc.
1606
1607
  - `headers` - Custom headers to include in requests.
1607
1608
  - `authHeader` - If true, adds `Authorization: Bearer` header automatically.
@@ -2556,6 +2557,7 @@ All examples in [examples/extensions/](../examples/extensions/).
2556
2557
  | `custom-compaction.ts` | Custom compaction summary | `on("session_before_compact")` |
2557
2558
  | `trigger-compact.ts` | Trigger compaction manually | `compact()` |
2558
2559
  | `git-checkpoint.ts` | Git stash on turns | `on("turn_start")`, `on("session_before_fork")`, `exec` |
2560
+ | `git-merge-and-resolve.ts` | Fetch, merge, and resolve conflicts | `on("agent_end")`, `exec`, `sendUserMessage` |
2559
2561
  | `auto-commit-on-exit.ts` | Commit on shutdown | `on("session_shutdown")`, `exec` |
2560
2562
  | **UI Components** |||
2561
2563
  | `status-line.ts` | Footer status indicator | `setStatus`, session events |
package/docs/index.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Pi Documentation
2
2
 
3
- Pi is a minimal terminal coding harness. It is designed to stay small at the core while being extended through TypeScript extensions, skills, prompt templates, themes, and pi packages.
3
+ Pi is a minimal terminal coding harness. It is designed to stay small at the core while being extended through TypeScript extensions, skills, rules, prompt templates, themes, and pi packages.
4
4
 
5
5
  ## Quick start
6
6
 
@@ -50,9 +50,10 @@ For the full first-run flow, see [Quickstart](quickstart.md).
50
50
 
51
51
  - [Extensions](extensions.md) - TypeScript modules for tools, commands, events, and custom UI.
52
52
  - [Skills](skills.md) - Agent Skills for reusable on-demand capabilities.
53
+ - [Rules](rules.md) - on-demand mandatory constraints and policies.
53
54
  - [Prompt templates](prompt-templates.md) - reusable prompts that expand from slash commands.
54
55
  - [Themes](themes.md) - built-in and custom terminal themes.
55
- - [Pi packages](packages.md) - bundle and share extensions, skills, prompts, and themes.
56
+ - [Pi packages](packages.md) - bundle and share extensions, skills, rules, prompts, and themes.
56
57
  - [Custom models](models.md) - add model entries for supported provider APIs.
57
58
  - [Custom providers](custom-provider.md) - implement custom APIs and OAuth flows.
58
59
 
package/docs/models.md CHANGED
@@ -101,7 +101,7 @@ Use `google-generative-ai` with a `baseUrl` to add models from Google AI Studio,
101
101
  "my-google": {
102
102
  "baseUrl": "https://generativelanguage.googleapis.com/v1beta",
103
103
  "api": "google-generative-ai",
104
- "apiKey": "GEMINI_API_KEY",
104
+ "apiKey": "$GEMINI_API_KEY",
105
105
  "models": [
106
106
  {
107
107
  "id": "gemma-4-31b-it",
@@ -143,22 +143,31 @@ Set `api` at provider level (default for all models) or model level (override pe
143
143
 
144
144
  ### Value Resolution
145
145
 
146
- The `apiKey` and `headers` fields support three formats:
146
+ The `apiKey` and `headers` fields support command execution, environment interpolation, and literals:
147
147
 
148
- - **Shell command:** `"!command"` executes and uses stdout
148
+ - **Shell command:** `"!command"` at the start executes the whole value as a command and uses stdout
149
149
  ```json
150
150
  "apiKey": "!security find-generic-password -ws 'anthropic'"
151
151
  "apiKey": "!op read 'op://vault/item/credential'"
152
152
  ```
153
- - **Environment variable:** Uses the value of the named variable
153
+ - **Environment interpolation:** `"$ENV_VAR"` or `"${ENV_VAR}"` uses the value of the named variable. Interpolation works inside larger literals.
154
154
  ```json
155
- "apiKey": "MY_API_KEY"
155
+ "apiKey": "$MY_API_KEY"
156
+ "apiKey": "${KEY_PREFIX}_${KEY_SUFFIX}"
157
+ ```
158
+ `$FOO_BAR` is the variable `FOO_BAR`; use `${FOO}_BAR` when `BAR` is literal text. Missing environment variables make the value unresolved.
159
+ - **Escapes:** `"$$"` emits a literal `"$"`; `"$!"` emits a literal `"!"` without triggering command execution.
160
+ ```json
161
+ "apiKey": "$$literal-dollar-prefix"
162
+ "apiKey": "$!literal-bang-prefix"
156
163
  ```
157
164
  - **Literal value:** Used directly
158
165
  ```json
159
166
  "apiKey": "sk-..."
160
167
  ```
161
168
 
169
+ Legacy uppercase env-var-like values such as `MY_API_KEY` are migrated to `$MY_API_KEY` on startup.
170
+
162
171
  For `models.json`, shell commands are resolved at request time. pi intentionally does not apply built-in TTL, stale reuse, or recovery logic for arbitrary commands. Different commands need different caching and failure strategies, and pi cannot infer the right one.
163
172
 
164
173
  If your command is slow, expensive, rate-limited, or should keep using a previous value on transient failures, wrap it in your own script or command that implements the caching or TTL behavior you want.
@@ -172,10 +181,10 @@ If your command is slow, expensive, rate-limited, or should keep using a previou
172
181
  "providers": {
173
182
  "custom-proxy": {
174
183
  "baseUrl": "https://proxy.example.com/v1",
175
- "apiKey": "MY_API_KEY",
184
+ "apiKey": "$MY_API_KEY",
176
185
  "api": "anthropic-messages",
177
186
  "headers": {
178
- "x-portkey-api-key": "PORTKEY_API_KEY",
187
+ "x-portkey-api-key": "$PORTKEY_API_KEY",
179
188
  "x-secret": "!op read 'op://vault/item/secret'"
180
189
  },
181
190
  "models": [...]
@@ -268,7 +277,7 @@ To merge custom models into a built-in provider, include the `models` array:
268
277
  "providers": {
269
278
  "anthropic": {
270
279
  "baseUrl": "https://my-proxy.example.com/v1",
271
- "apiKey": "ANTHROPIC_API_KEY",
280
+ "apiKey": "$ANTHROPIC_API_KEY",
272
281
  "api": "anthropic-messages",
273
282
  "models": [...]
274
283
  }
@@ -319,16 +328,24 @@ For providers or proxies using `api: "anthropic-messages"`, use `compat.supports
319
328
 
320
329
  By default pi sends per-tool `eager_input_streaming: true`. If a proxy or Anthropic-compatible backend rejects that field, set `supportsEagerToolInputStreaming` to `false`. Pi will omit `tools[].eager_input_streaming` and send the legacy `fine-grained-tool-streaming-2025-05-14` beta header for tool-enabled requests instead.
321
330
 
331
+ Some Anthropic models require adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`) instead of the legacy budget-based thinking payload. Built-in models set this automatically. For custom providers or aliases that route to those models, set `forceAdaptiveThinking` to `true`.
332
+
333
+ Some Anthropic-compatible providers emit thinking blocks with empty signatures and still expect them on replay. Set `allowEmptySignature` to `true` only for those providers; real Anthropic rejects empty thinking signatures.
334
+
335
+
322
336
  ```json
323
337
  {
324
338
  "providers": {
325
339
  "anthropic-proxy": {
326
340
  "baseUrl": "https://proxy.example.com",
327
341
  "api": "anthropic-messages",
328
- "apiKey": "ANTHROPIC_PROXY_KEY",
342
+ "apiKey": "$ANTHROPIC_PROXY_KEY",
329
343
  "compat": {
330
344
  "supportsEagerToolInputStreaming": false,
331
- "supportsLongCacheRetention": true
345
+ "supportsLongCacheRetention": true,
346
+ "forceAdaptiveThinking": true,
347
+ "allowEmptySignature": true
348
+
332
349
  },
333
350
  "models": [
334
351
  {
@@ -346,6 +363,11 @@ By default pi sends per-tool `eager_input_streaming: true`. If a proxy or Anthro
346
363
  |-------|-------------|
347
364
  | `supportsEagerToolInputStreaming` | Whether the provider accepts per-tool `eager_input_streaming`. Default: `true`. Set to `false` to omit that field and use the legacy fine-grained tool streaming beta header on tool-enabled requests. |
348
365
  | `supportsLongCacheRetention` | Whether the provider accepts Anthropic long cache retention (`cache_control.ttl: "1h"`) when cache retention is `long`. Default: `true`. |
366
+ | `sendSessionAffinityHeaders` | Whether to send `x-session-affinity` from the session id when caching is enabled. Default: auto-detected for known providers. |
367
+ | `supportsCacheControlOnTools` | Whether the provider accepts Anthropic-style `cache_control` markers on tool definitions. Default: `true`. |
368
+ | `forceAdaptiveThinking` | Whether to send adaptive thinking (`thinking.type: "adaptive"` plus `output_config.effort`) for this model. Built-in adaptive models set this automatically. Default: `false`. |
369
+ | `allowEmptySignature` | Whether to replay empty thinking signatures as `signature: ""` instead of converting thinking to text. Default: `false`. |
370
+
349
371
 
350
372
  ## OpenAI Compatibility
351
373
 
@@ -399,7 +421,7 @@ Example:
399
421
  "providers": {
400
422
  "openrouter": {
401
423
  "baseUrl": "https://openrouter.ai/api/v1",
402
- "apiKey": "OPENROUTER_API_KEY",
424
+ "apiKey": "$OPENROUTER_API_KEY",
403
425
  "api": "openai-completions",
404
426
  "models": [
405
427
  {
@@ -449,7 +471,7 @@ Vercel AI Gateway example:
449
471
  "providers": {
450
472
  "vercel-ai-gateway": {
451
473
  "baseUrl": "https://ai-gateway.vercel.sh/v1",
452
- "apiKey": "AI_GATEWAY_API_KEY",
474
+ "apiKey": "$AI_GATEWAY_API_KEY",
453
475
  "api": "openai-completions",
454
476
  "models": [
455
477
  {
package/docs/packages.md CHANGED
@@ -1,8 +1,8 @@
1
- > pi can help you create pi packages. Ask it to bundle your extensions, skills, prompt templates, or themes.
1
+ > pi can help you create pi packages. Ask it to bundle your extensions, skills, rules, prompt templates, or themes.
2
2
 
3
3
  # Pi Packages
4
4
 
5
- Pi packages bundle extensions, skills, prompt templates, and themes so you can share them through npm or git. A package can declare resources in `package.json` under the `pi` key, or use conventional directories.
5
+ Pi packages bundle extensions, skills, rules, prompt templates, and themes so you can share them through npm or git. A package can declare resources in `package.json` under the `pi` key, or use conventional directories.
6
6
 
7
7
  ## Table of Contents
8
8
 
@@ -28,8 +28,8 @@ pi install ./relative/path/to/package
28
28
 
29
29
  pi remove npm:@foo/bar
30
30
  pi list # show installed packages from settings
31
- pi update # update pi and all non-pinned packages
32
- pi update --extensions # update all non-pinned packages only
31
+ pi update # update pi, update packages, and reconcile pinned git refs
32
+ pi update --extensions # update packages and reconcile pinned git refs only
33
33
  pi update --self # update pi only
34
34
  pi update --self --force # reinstall pi even if current
35
35
  pi update npm:@foo/bar # update one package
@@ -85,9 +85,10 @@ ssh://git@github.com/user/repo@v1
85
85
  - HTTPS and SSH URLs are both supported.
86
86
  - SSH URLs use your configured SSH keys automatically (respects `~/.ssh/config`).
87
87
  - For non-interactive runs (for example CI), you can set `GIT_TERMINAL_PROMPT=0` to disable credential prompts and set `GIT_SSH_COMMAND` (for example `ssh -o BatchMode=yes -o ConnectTimeout=5`) to fail fast.
88
- - Refs are pinned tags or commits and skip package updates (`pi update`, `pi update --extensions`). Use `pi install git:host/user/repo@new-ref` to move an existing package to a new pinned ref.
88
+ - Refs are pinned tags or commits. `pi update` and `pi update --extensions` do not move them to newer refs, but they do reconcile an existing clone to the configured ref.
89
+ - Use `pi install git:host/user/repo@new-ref` to update settings and move an existing package to a new pinned ref.
89
90
  - Cloned to `~/.pi/agent/git/<host>/<path>` (global) or `.pi/git/<host>/<path>` (project).
90
- - Runs `npm install` after clone, pull, or pinned ref change if `package.json` exists.
91
+ - When reconciliation changes the checkout, pi resets and cleans the clone, then runs `npm install` if `package.json` exists.
91
92
 
92
93
  **SSH examples:**
93
94
  ```bash
@@ -121,6 +122,7 @@ Add a `pi` manifest to `package.json` or use conventional directories. Include t
121
122
  "pi": {
122
123
  "extensions": ["./extensions"],
123
124
  "skills": ["./skills"],
125
+ "rules": ["./rules"],
124
126
  "prompts": ["./prompts"],
125
127
  "themes": ["./themes"]
126
128
  }
@@ -158,12 +160,13 @@ If no `pi` manifest is present, pi auto-discovers resources from these directori
158
160
 
159
161
  - `extensions/` loads `.ts` and `.js` files
160
162
  - `skills/` recursively finds `SKILL.md` folders and loads top-level `.md` files as skills
163
+ - `rules/` recursively finds `RULES.md` folders and loads top-level `.md` files as rules
161
164
  - `prompts/` loads `.md` files
162
165
  - `themes/` loads `.json` files
163
166
 
164
167
  ## Dependencies
165
168
 
166
- Third party runtime dependencies belong in `dependencies` in `package.json`. Dependencies that do not register extensions, skills, prompt templates, or themes also belong in `dependencies`. When pi installs a package from npm or git, it runs `npm install`, so those dependencies are installed automatically.
169
+ Third party runtime dependencies belong in `dependencies` in `package.json`. Dependencies that do not register extensions, skills, rules, prompt templates, or themes also belong in `dependencies`. When pi installs a package from npm or git, it runs `npm install`, so those dependencies are installed automatically.
167
170
 
168
171
  Pi bundles core packages for extensions and skills. If you import any of these, list them in `peerDependencies` with a `"*"` range and do not bundle them: `@fleetagent/pi-ai`, `@fleetagent/pi-agent-core`, `@fleetagent/pi-coding-agent`, `@fleetagent/pi-tui`, `typebox`.
169
172
 
@@ -214,7 +217,7 @@ Filter what a package loads using the object form in settings:
214
217
 
215
218
  ## Enable and Disable Resources
216
219
 
217
- Use `pi config` to enable or disable extensions, skills, prompt templates, and themes from installed packages and local directories. Works for both global (`~/.pi/agent`) and project (`.pi/`) scopes.
220
+ Use `pi config` to enable or disable extensions, skills, rules, prompt templates, and themes from installed packages and local directories. Works for both global (`~/.pi/agent`) and project (`.pi/`) scopes.
218
221
 
219
222
  ## Scope and Deduplication
220
223
 
package/docs/providers.md CHANGED
@@ -101,23 +101,31 @@ The file is created with `0600` permissions (user read/write only). Auth file cr
101
101
 
102
102
  ### Key Resolution
103
103
 
104
- The `key` field supports three formats:
104
+ The `key` field supports command execution, environment interpolation, and literals:
105
105
 
106
- - **Shell command:** `"!command"` executes and uses stdout (cached for process lifetime)
106
+ - **Shell command:** `"!command"` at the start executes the whole value as a command and uses stdout (cached for process lifetime)
107
107
  ```json
108
108
  { "type": "api_key", "key": "!security find-generic-password -ws 'anthropic'" }
109
109
  { "type": "api_key", "key": "!op read 'op://vault/item/credential'" }
110
110
  ```
111
- - **Environment variable:** Uses the value of the named variable
111
+ - **Environment interpolation:** `"$ENV_VAR"` or `"${ENV_VAR}"` uses the value of the named variable. Interpolation works inside larger literals.
112
112
  ```json
113
- { "type": "api_key", "key": "MY_ANTHROPIC_KEY" }
113
+ { "type": "api_key", "key": "$MY_ANTHROPIC_KEY" }
114
+ { "type": "api_key", "key": "${KEY_PREFIX}_${KEY_SUFFIX}" }
115
+ ```
116
+ `$FOO_BAR` is the variable `FOO_BAR`; use `${FOO}_BAR` when `BAR` is literal text. Missing environment variables make the value unresolved.
117
+ - **Escapes:** `"$$"` emits a literal `"$"`; `"$!"` emits a literal `"!"` without triggering command execution.
118
+ ```json
119
+ { "type": "api_key", "key": "$$literal-dollar-prefix" }
120
+ { "type": "api_key", "key": "$!literal-bang-prefix" }
114
121
  ```
115
122
  - **Literal value:** Used directly
116
123
  ```json
117
124
  { "type": "api_key", "key": "sk-ant-..." }
125
+ { "type": "api_key", "key": "public" }
118
126
  ```
119
127
 
120
- OAuth credentials are also stored here after `/login` and managed automatically.
128
+ Legacy uppercase env-var-like values such as `MY_API_KEY` are migrated to `$MY_API_KEY` on startup. OAuth credentials are also stored here after `/login` and managed automatically.
121
129
 
122
130
  ## Cloud Providers
123
131
 
@@ -159,6 +159,6 @@ Use `--mode json` for JSON event output or `--mode rpc` for process integration.
159
159
  - [Providers](providers.md) - authentication and model setup.
160
160
  - [Settings](settings.md) - global and project configuration.
161
161
  - [Keybindings](keybindings.md) - shortcuts and customization.
162
- - [Pi Packages](packages.md) - install shared extensions, skills, prompts, and themes.
162
+ - [Pi Packages](packages.md) - install shared extensions, skills, rules, prompts, and themes.
163
163
 
164
164
  Platform notes: [Windows](windows.md), [Termux](termux.md), [tmux](tmux.md), [Terminal setup](terminal-setup.md), [Shell aliases](shell-aliases.md).
package/docs/rpc.md CHANGED
@@ -700,7 +700,7 @@ The current session name is available via `get_state` in the `sessionName` field
700
700
 
701
701
  #### get_commands
702
702
 
703
- Get available commands (extension commands, prompt templates, and skills). These can be invoked via the `prompt` command by prefixing with `/`.
703
+ Get available commands (extension commands, prompt templates, skills, and rules). These can be invoked via the `prompt` command by prefixing with `/`.
704
704
 
705
705
  ```json
706
706
  {"type": "get_commands"}
@@ -716,7 +716,8 @@ Response:
716
716
  "commands": [
717
717
  {"name": "session-name", "description": "Set or clear session name", "source": "extension", "path": "/home/user/.pi/agent/extensions/session.ts"},
718
718
  {"name": "fix-tests", "description": "Fix failing tests", "source": "prompt", "location": "project", "path": "/home/user/myproject/.pi/agent/prompts/fix-tests.md"},
719
- {"name": "skill:brave-search", "description": "Web search via Brave API", "source": "skill", "location": "user", "path": "/home/user/.pi/agent/skills/brave-search/SKILL.md"}
719
+ {"name": "skill:brave-search", "description": "Web search via Brave API", "source": "skill", "location": "user", "path": "/home/user/.pi/agent/skills/brave-search/SKILL.md"},
720
+ {"name": "rule:typescript", "description": "Mandatory TypeScript rules", "source": "rule", "location": "project", "path": "/home/user/myproject/.pi/rules/typescript/RULES.md"}
720
721
  ]
721
722
  }
722
723
  }
@@ -729,6 +730,7 @@ Each command has:
729
730
  - `"extension"`: Registered via `pi.registerCommand()` in an extension
730
731
  - `"prompt"`: Loaded from a prompt template `.md` file
731
732
  - `"skill"`: Loaded from a skill directory (name is prefixed with `skill:`)
733
+ - `"rule"`: Loaded from a rule directory (name is prefixed with `rule:`)
732
734
  - `location`: Where it was loaded from (optional, not present for extensions):
733
735
  - `"user"`: User-level (`~/.pi/agent/`)
734
736
  - `"project"`: Project-level (`./.pi/agent/`)
package/docs/rules.md ADDED
@@ -0,0 +1,102 @@
1
+ # Rules
2
+
3
+ Rules are on-demand, mandatory instruction files. Use them for constraints and policies that must apply when the current task or files match the rule description.
4
+
5
+ Rules use progressive disclosure like skills: pi lists available rules in the system prompt, and the agent loads the full file only when applicable.
6
+
7
+ ## Locations
8
+
9
+ Pi loads rules from:
10
+
11
+ - Global:
12
+ - `~/.pi/agent/rules/`
13
+ - `~/.agents/rules/`
14
+ - Project:
15
+ - `.pi/rules/`
16
+ - `.agents/rules/` in `cwd` and ancestor directories (up to git repo root, or filesystem root when not in a repo)
17
+ - Packages: `rules/` directories or `pi.rules` entries in `package.json`
18
+ - Settings: `rules` array with files or directories
19
+ - CLI: `--rule <path>` (repeatable, additive even with `--no-rules`)
20
+
21
+ Discovery rules:
22
+
23
+ - In `~/.pi/agent/rules/` and `.pi/rules/`, direct root `.md` files are discovered as individual rules
24
+ - In all rule locations, directories containing `RULES.md` are discovered recursively
25
+ - In `~/.agents/rules/` and project `.agents/rules/`, root `.md` files are ignored
26
+
27
+ Disable discovery with `--no-rules` (explicit `--rule` paths still load).
28
+
29
+ ## How Rules Work
30
+
31
+ 1. At startup, pi scans rule locations and extracts names and descriptions
32
+ 2. The system prompt includes available rules in XML format
33
+ 3. When a task or file matches, the agent uses `read` to load the full `RULES.md`
34
+ 4. Applicable rules are mandatory and constrain normal behavior and skills
35
+
36
+ Only descriptions are always in context. Full instructions load on demand.
37
+
38
+ ## Rule Commands
39
+
40
+ Rules register as `/rule:name` commands when skill commands are enabled:
41
+
42
+ ```bash
43
+ /rule:typescript # Load the rule
44
+ /rule:naming-conventions use for new files
45
+ ```
46
+
47
+ Arguments after the command are appended to the rule content as user text.
48
+
49
+ ## Rule Structure
50
+
51
+ A rule is a directory with a `RULES.md` file. Everything else is freeform.
52
+
53
+ ```text
54
+ typescript/
55
+ ├── RULES.md
56
+ └── references/
57
+ └── examples.md
58
+ ```
59
+
60
+ ### RULES.md Format
61
+
62
+ ```markdown
63
+ ---
64
+ name: typescript
65
+ description: Mandatory TypeScript rules. Load before editing *.ts or *.tsx files.
66
+ ---
67
+
68
+ # TypeScript Rules
69
+
70
+ - No `any` unless absolutely necessary.
71
+ - Use top-level imports only.
72
+ ```
73
+
74
+ Use relative paths from the rule directory:
75
+
76
+ ```markdown
77
+ See [examples](references/examples.md).
78
+ ```
79
+
80
+ ## Frontmatter
81
+
82
+ | Field | Required | Description |
83
+ |-------|----------|-------------|
84
+ | `name` | Yes | Max 64 chars. Lowercase a-z, 0-9, hyphens. Falls back to parent directory name. |
85
+ | `description` | Yes | Max 1024 chars. Defines when the rule applies. |
86
+ | `disable-model-invocation` | No | When `true`, the rule is hidden from the system prompt. Users must use `/rule:name`. |
87
+
88
+ ## Description Best Practices
89
+
90
+ The description determines when the agent loads the rule. Write applicability, not capability.
91
+
92
+ Good:
93
+
94
+ ```yaml
95
+ description: Mandatory TypeScript rules. Load before editing *.ts or *.tsx files.
96
+ ```
97
+
98
+ Poor:
99
+
100
+ ```yaml
101
+ description: TypeScript help.
102
+ ```