@earendil-works/pi-coding-agent 0.99.2 → 1.0.1

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 (225) hide show
  1. package/CHANGELOG.md +90 -0
  2. package/README.md +32 -7
  3. package/dist/bundle/chunks/{anthropic-messages-UID2PALB.js → anthropic-messages-GXHZP2HL.js} +3 -3
  4. package/dist/bundle/chunks/anthropic.js +2 -2
  5. package/dist/bundle/chunks/{azure-openai-responses-TLHNIYZK.js → azure-openai-responses-AFMVM6NP.js} +1 -1
  6. package/dist/bundle/chunks/bedrock-converse-stream.js +1 -1
  7. package/dist/bundle/chunks/chunk-2KAZSACP.js +2 -0
  8. package/dist/bundle/chunks/{chunk-R5J3MOBF.js → chunk-2VNGUWBO.js} +1 -1
  9. package/dist/bundle/chunks/chunk-5OEJBNHG.js +1203 -0
  10. package/dist/bundle/chunks/{chunk-7QSPBJKW.js → chunk-6FX7UEPL.js} +16 -30
  11. package/dist/bundle/chunks/chunk-F5ZABL6Z.js +20 -0
  12. package/dist/bundle/chunks/chunk-FDW3AYMM.js +2 -0
  13. package/dist/bundle/chunks/chunk-OYBSLV7Y.js +16 -0
  14. package/dist/bundle/chunks/{chunk-45SXDISG.js → chunk-PDFMCAOZ.js} +5 -5
  15. package/dist/bundle/chunks/{chunk-G3UZ5FWD.js → chunk-SYF2TF6W.js} +11 -60
  16. package/dist/bundle/chunks/chunk-WCQZGZ6I.js +82 -0
  17. package/dist/bundle/chunks/chunk-ZSI3BL5Q.js +4 -0
  18. package/dist/bundle/chunks/{cli-ALNBTL3U.js → cli-KILDDGW3.js} +4 -4
  19. package/dist/bundle/chunks/{cloudflare-workers-ai-system-one-NPQN263E.js → cloudflare-workers-ai-system-one-X5PVWWYJ.js} +1 -1
  20. package/dist/bundle/chunks/codemode-worker.js +68 -8
  21. package/dist/bundle/chunks/easter-egg-3d-T5FFWIBR.js +2 -0
  22. package/dist/bundle/chunks/execute-RYVK27GT.js +388 -0
  23. package/dist/bundle/chunks/node-N5BSDOA5.js +16 -0
  24. package/dist/bundle/chunks/openai-chatgpt.js +2 -2
  25. package/dist/bundle/chunks/{openai-codex-responses-3GKGNXC4.js → openai-codex-responses-GK67DQQJ.js} +1 -1
  26. package/dist/bundle/chunks/openai-codex.js +1 -1
  27. package/dist/bundle/chunks/{openai-responses-S7GKCCDG.js → openai-responses-CQDDLDQF.js} +1 -1
  28. package/dist/bundle/chunks/openrouter.js +1 -1
  29. package/dist/bundle/chunks/radius.js +1 -1
  30. package/dist/bundle/chunks/runtime-D5I2NTIF.js +2 -0
  31. package/dist/bundle/chunks/virtual-modules-OO3V5ZPE.js +2 -0
  32. package/dist/bundle/cli-runtime.js +1 -1
  33. package/dist/bundle/index.js +1 -1
  34. package/dist/bundle/rpc-entry.js +1 -1
  35. package/dist/cli/args.d.ts.map +1 -1
  36. package/dist/cli/args.js +6 -3
  37. package/dist/cli/args.js.map +1 -1
  38. package/dist/config.d.ts.map +1 -1
  39. package/dist/config.js +3 -0
  40. package/dist/config.js.map +1 -1
  41. package/dist/core/agent-session.d.ts +8 -0
  42. package/dist/core/agent-session.d.ts.map +1 -1
  43. package/dist/core/agent-session.js +38 -9
  44. package/dist/core/agent-session.js.map +1 -1
  45. package/dist/core/export-html/tool-renderer.d.ts +3 -3
  46. package/dist/core/export-html/tool-renderer.d.ts.map +1 -1
  47. package/dist/core/export-html/tool-renderer.js +3 -3
  48. package/dist/core/export-html/tool-renderer.js.map +1 -1
  49. package/dist/core/extensions/index.d.ts +1 -1
  50. package/dist/core/extensions/index.d.ts.map +1 -1
  51. package/dist/core/extensions/index.js.map +1 -1
  52. package/dist/core/extensions/loader.d.ts.map +1 -1
  53. package/dist/core/extensions/loader.js +5 -0
  54. package/dist/core/extensions/loader.js.map +1 -1
  55. package/dist/core/extensions/runner.d.ts +3 -1
  56. package/dist/core/extensions/runner.d.ts.map +1 -1
  57. package/dist/core/extensions/runner.js +6 -0
  58. package/dist/core/extensions/runner.js.map +1 -1
  59. package/dist/core/extensions/types.d.ts +9 -0
  60. package/dist/core/extensions/types.d.ts.map +1 -1
  61. package/dist/core/extensions/types.js.map +1 -1
  62. package/dist/core/mcp-servers.d.ts +12 -0
  63. package/dist/core/mcp-servers.d.ts.map +1 -1
  64. package/dist/core/mcp-servers.js +18 -0
  65. package/dist/core/mcp-servers.js.map +1 -1
  66. package/dist/core/model-registry.d.ts +3 -1
  67. package/dist/core/model-registry.d.ts.map +1 -1
  68. package/dist/core/model-registry.js +4 -0
  69. package/dist/core/model-registry.js.map +1 -1
  70. package/dist/core/model-resolver.js +1 -1
  71. package/dist/core/model-resolver.js.map +1 -1
  72. package/dist/core/radius.d.ts +2 -0
  73. package/dist/core/radius.d.ts.map +1 -1
  74. package/dist/core/radius.js +2 -0
  75. package/dist/core/radius.js.map +1 -1
  76. package/dist/core/settings-manager.d.ts +7 -5
  77. package/dist/core/settings-manager.d.ts.map +1 -1
  78. package/dist/core/settings-manager.js +3 -2
  79. package/dist/core/settings-manager.js.map +1 -1
  80. package/dist/core/system-prompt.js +1 -1
  81. package/dist/core/system-prompt.js.map +1 -1
  82. package/dist/core/tools/bash.d.ts.map +1 -1
  83. package/dist/core/tools/bash.js +3 -5
  84. package/dist/core/tools/bash.js.map +1 -1
  85. package/dist/core/tools/renderers/index.d.ts +2 -2
  86. package/dist/core/tools/renderers/index.d.ts.map +1 -1
  87. package/dist/core/tools/renderers/index.js.map +1 -1
  88. package/dist/extensions/codemode/execute.d.ts.map +1 -1
  89. package/dist/extensions/codemode/execute.js +154 -47
  90. package/dist/extensions/codemode/execute.js.map +1 -1
  91. package/dist/extensions/codemode/tool.d.ts +7 -7
  92. package/dist/extensions/codemode/tool.d.ts.map +1 -1
  93. package/dist/extensions/codemode/tool.js +55 -102
  94. package/dist/extensions/codemode/tool.js.map +1 -1
  95. package/dist/extensions/mcp/cli.d.ts.map +1 -1
  96. package/dist/extensions/mcp/cli.js +5 -2
  97. package/dist/extensions/mcp/cli.js.map +1 -1
  98. package/dist/extensions/mcp/config.d.ts +15 -3
  99. package/dist/extensions/mcp/config.d.ts.map +1 -1
  100. package/dist/extensions/mcp/config.js +46 -10
  101. package/dist/extensions/mcp/config.js.map +1 -1
  102. package/dist/extensions/mcp/index.d.ts +4 -1
  103. package/dist/extensions/mcp/index.d.ts.map +1 -1
  104. package/dist/extensions/mcp/index.js +97 -51
  105. package/dist/extensions/mcp/index.js.map +1 -1
  106. package/dist/extensions/mcp/oauth.d.ts +12 -8
  107. package/dist/extensions/mcp/oauth.d.ts.map +1 -1
  108. package/dist/extensions/mcp/oauth.js +104 -35
  109. package/dist/extensions/mcp/oauth.js.map +1 -1
  110. package/dist/extensions/mcp/runtime.d.ts.map +1 -1
  111. package/dist/extensions/mcp/runtime.js +3 -1
  112. package/dist/extensions/mcp/runtime.js.map +1 -1
  113. package/dist/extensions/mcp/tools.d.ts +3 -1
  114. package/dist/extensions/mcp/tools.d.ts.map +1 -1
  115. package/dist/extensions/mcp/tools.js +19 -13
  116. package/dist/extensions/mcp/tools.js.map +1 -1
  117. package/dist/extensions/mcp/ui.d.ts.map +1 -1
  118. package/dist/extensions/mcp/ui.js +8 -4
  119. package/dist/extensions/mcp/ui.js.map +1 -1
  120. package/dist/index.d.ts +2 -2
  121. package/dist/index.d.ts.map +1 -1
  122. package/dist/index.js.map +1 -1
  123. package/dist/main.d.ts.map +1 -1
  124. package/dist/main.js +6 -0
  125. package/dist/main.js.map +1 -1
  126. package/dist/modes/interactive/components/armin.d.ts +3 -0
  127. package/dist/modes/interactive/components/armin.d.ts.map +1 -1
  128. package/dist/modes/interactive/components/armin.js +7 -5
  129. package/dist/modes/interactive/components/armin.js.map +1 -1
  130. package/dist/modes/interactive/components/auth-url.d.ts +14 -0
  131. package/dist/modes/interactive/components/auth-url.d.ts.map +1 -0
  132. package/dist/modes/interactive/components/auth-url.js +37 -0
  133. package/dist/modes/interactive/components/auth-url.js.map +1 -0
  134. package/dist/modes/interactive/components/easter-egg-3d.d.ts +104 -0
  135. package/dist/modes/interactive/components/easter-egg-3d.d.ts.map +1 -0
  136. package/dist/modes/interactive/components/easter-egg-3d.js +1139 -0
  137. package/dist/modes/interactive/components/easter-egg-3d.js.map +1 -0
  138. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts +6 -0
  139. package/dist/modes/interactive/components/easter-egg-3d.lazy.d.ts.map +1 -0
  140. package/dist/modes/interactive/components/easter-egg-3d.lazy.js +23 -0
  141. package/dist/modes/interactive/components/easter-egg-3d.lazy.js.map +1 -0
  142. package/dist/modes/interactive/components/index.d.ts +0 -1
  143. package/dist/modes/interactive/components/index.d.ts.map +1 -1
  144. package/dist/modes/interactive/components/index.js +0 -1
  145. package/dist/modes/interactive/components/index.js.map +1 -1
  146. package/dist/modes/interactive/components/login-dialog.d.ts +2 -0
  147. package/dist/modes/interactive/components/login-dialog.d.ts.map +1 -1
  148. package/dist/modes/interactive/components/login-dialog.js +11 -5
  149. package/dist/modes/interactive/components/login-dialog.js.map +1 -1
  150. package/dist/modes/interactive/components/oauth-selector.d.ts +8 -2
  151. package/dist/modes/interactive/components/oauth-selector.d.ts.map +1 -1
  152. package/dist/modes/interactive/components/oauth-selector.js +24 -21
  153. package/dist/modes/interactive/components/oauth-selector.js.map +1 -1
  154. package/dist/modes/interactive/components/pi-logo.d.ts +7 -0
  155. package/dist/modes/interactive/components/pi-logo.d.ts.map +1 -1
  156. package/dist/modes/interactive/components/pi-logo.js +13 -1
  157. package/dist/modes/interactive/components/pi-logo.js.map +1 -1
  158. package/dist/modes/interactive/components/radius-login-selector.d.ts +15 -0
  159. package/dist/modes/interactive/components/radius-login-selector.d.ts.map +1 -0
  160. package/dist/modes/interactive/components/radius-login-selector.js +85 -0
  161. package/dist/modes/interactive/components/radius-login-selector.js.map +1 -0
  162. package/dist/modes/interactive/components/settings-selector.d.ts +3 -3
  163. package/dist/modes/interactive/components/settings-selector.d.ts.map +1 -1
  164. package/dist/modes/interactive/components/settings-selector.js +5 -5
  165. package/dist/modes/interactive/components/settings-selector.js.map +1 -1
  166. package/dist/modes/interactive/components/tool-execution.d.ts +6 -18
  167. package/dist/modes/interactive/components/tool-execution.d.ts.map +1 -1
  168. package/dist/modes/interactive/components/tool-execution.js +22 -45
  169. package/dist/modes/interactive/components/tool-execution.js.map +1 -1
  170. package/dist/modes/interactive/components/user-message.d.ts.map +1 -1
  171. package/dist/modes/interactive/components/user-message.js +5 -4
  172. package/dist/modes/interactive/components/user-message.js.map +1 -1
  173. package/dist/modes/interactive/interactive-mode.d.ts +12 -2
  174. package/dist/modes/interactive/interactive-mode.d.ts.map +1 -1
  175. package/dist/modes/interactive/interactive-mode.js +166 -55
  176. package/dist/modes/interactive/interactive-mode.js.map +1 -1
  177. package/dist/modes/interactive/theme/system-theme.d.ts +2 -1
  178. package/dist/modes/interactive/theme/system-theme.d.ts.map +1 -1
  179. package/dist/modes/interactive/theme/system-theme.js +16 -5
  180. package/dist/modes/interactive/theme/system-theme.js.map +1 -1
  181. package/dist/package-manager-cli.d.ts.map +1 -1
  182. package/dist/package-manager-cli.js +12 -0
  183. package/dist/package-manager-cli.js.map +1 -1
  184. package/dist/utils/image-convert.d.ts +12 -0
  185. package/dist/utils/image-convert.d.ts.map +1 -1
  186. package/dist/utils/image-convert.js +45 -6
  187. package/dist/utils/image-convert.js.map +1 -1
  188. package/docs/cli.md +8 -16
  189. package/docs/codemode.md +207 -0
  190. package/docs/docs.json +5 -1
  191. package/docs/environment-variables.md +2 -2
  192. package/docs/extensions.md +4 -0
  193. package/docs/index.md +1 -1
  194. package/docs/keybindings.md +1 -1
  195. package/docs/mcp.md +40 -1
  196. package/docs/models.md +24 -3
  197. package/docs/providers.md +16 -6
  198. package/docs/quickstart.md +16 -2
  199. package/docs/sdk.md +1 -1
  200. package/docs/settings.md +3 -3
  201. package/docs/usage.md +1 -1
  202. package/examples/extensions/custom-provider-anthropic/package-lock.json +2 -2
  203. package/examples/extensions/custom-provider-anthropic/package.json +1 -1
  204. package/examples/extensions/custom-provider-gitlab-duo/package.json +1 -1
  205. package/examples/extensions/gondolin/package-lock.json +2 -2
  206. package/examples/extensions/gondolin/package.json +1 -1
  207. package/examples/extensions/sandbox/package-lock.json +2 -2
  208. package/examples/extensions/sandbox/package.json +1 -1
  209. package/examples/extensions/with-deps/package-lock.json +2 -2
  210. package/examples/extensions/with-deps/package.json +1 -1
  211. package/package.json +13 -14
  212. package/dist/bundle/chunks/chunk-3KYEVQKX.js +0 -82
  213. package/dist/bundle/chunks/chunk-3NVH2HQH.js +0 -4
  214. package/dist/bundle/chunks/chunk-3YAHQSW6.js +0 -1438
  215. package/dist/bundle/chunks/chunk-DTD7JQ7Y.js +0 -42
  216. package/dist/bundle/chunks/chunk-ECRK44UV.js +0 -2
  217. package/dist/bundle/chunks/execute-RC7W4ML3.js +0 -328
  218. package/dist/bundle/chunks/node-O3RIBJRH.js +0 -16
  219. package/dist/bundle/chunks/runtime-NJPX4ID2.js +0 -2
  220. package/dist/bundle/chunks/virtual-modules-5UGH4HRZ.js +0 -2
  221. package/dist/modes/interactive/components/daxnuts.d.ts +0 -23
  222. package/dist/modes/interactive/components/daxnuts.d.ts.map +0 -1
  223. package/dist/modes/interactive/components/daxnuts.js +0 -140
  224. package/dist/modes/interactive/components/daxnuts.js.map +0 -1
  225. package/npm-shrinkwrap.json +0 -1957
@@ -25,6 +25,7 @@ import { createCompactionSummaryMessage, createCustomMessage } from "../../core/
25
25
  import { defaultModelPerProvider, findExactModelReferenceMatch, resolveModelScopeFromModels, } from "../../core/model-resolver.js";
26
26
  import { CredentialSynchronizationError } from "../../core/model-runtime.js";
27
27
  import { DefaultPackageManager } from "../../core/package-manager.js";
28
+ import { RADIUS_MCP_URL, RADIUS_PROVIDER_ID } from "../../core/radius.js";
28
29
  import { formatMissingSessionCwdPrompt, MissingSessionCwdError } from "../../core/session-cwd.js";
29
30
  import { SessionManager, sessionEntryToContextMessages, } from "../../core/session-manager.js";
30
31
  import { BUILTIN_SLASH_COMMANDS } from "../../core/slash-commands.js";
@@ -32,10 +33,12 @@ import { isInstallTelemetryEnabled } from "../../core/telemetry.js";
32
33
  import { withBuiltInRenderers } from "../../core/tools/renderers/index.js";
33
34
  import { hasTrustRequiringProjectResources, ProjectTrustStore } from "../../core/trust-manager.js";
34
35
  import { getUsageCostBreakdown } from "../../core/usage-totals.js";
36
+ import { addMcpServerConfig, loadMcpConfig } from "../../extensions/mcp/config.js";
35
37
  import { getChangelogPath, getNewEntries, normalizeChangelogLinks, parseChangelog } from "../../utils/changelog.js";
36
38
  import { copyToClipboard, readClipboardFilePaths, readClipboardText } from "../../utils/clipboard.js";
37
39
  import { extensionForImageMimeType, readClipboardImage } from "../../utils/clipboard-image.js";
38
40
  import { parseGitUrl } from "../../utils/git.js";
41
+ import { ensurePngTranscoder } from "../../utils/image-convert.js";
39
42
  import { getCwdRelativePath } from "../../utils/paths.js";
40
43
  import { getPiUserAgent } from "../../utils/pi-user-agent.js";
41
44
  import { killTrackedDetachedChildren } from "../../utils/shell.js";
@@ -52,9 +55,9 @@ import { CompactionSummaryMessageComponent } from "./components/compaction-summa
52
55
  import { CustomEditor } from "./components/custom-editor.js";
53
56
  import { CustomEntryComponent } from "./components/custom-entry.js";
54
57
  import { CustomMessageComponent } from "./components/custom-message.js";
55
- import { DaxnutsComponent } from "./components/daxnuts.js";
56
58
  import { DynamicBorder } from "./components/dynamic-border.js";
57
59
  import { EarendilAnnouncementComponent } from "./components/earendil-announcement.js";
60
+ import { playArmin3d, playPiLogo3d } from "./components/easter-egg-3d.lazy.js";
58
61
  import { ExtensionEditorComponent } from "./components/extension-editor.js";
59
62
  import { ExtensionInputComponent } from "./components/extension-input.js";
60
63
  import { ExtensionSelectorComponent } from "./components/extension-selector.js";
@@ -63,8 +66,9 @@ import { formatKeyText, keyDisplayText, keyHint, keyText, rawKeyHint } from "./c
63
66
  import { LoginDialogComponent } from "./components/login-dialog.js";
64
67
  import { createMermaidMarkdownTransformer } from "./components/mermaid.js";
65
68
  import { ModelSelectorComponent } from "./components/model-selector.js";
66
- import { formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
67
- import { piLogoLines } from "./components/pi-logo.js";
69
+ import { formatAuthSelectorProviderStatus, formatAuthSelectorProviderType, OAuthSelectorComponent, } from "./components/oauth-selector.js";
70
+ import { piLogoLines, piWordmark, supportsPiLogo } from "./components/pi-logo.js";
71
+ import { createLoginMenuSelector } from "./components/radius-login-selector.js";
68
72
  import { ScopedModelsSelectorComponent } from "./components/scoped-models-selector.js";
69
73
  import { SessionSelectorComponent } from "./components/session-selector.js";
70
74
  import { SettingsSelectorComponent } from "./components/settings-selector.js";
@@ -106,6 +110,16 @@ class ExpandableText extends ThemedText {
106
110
  this.invalidate();
107
111
  }
108
112
  }
113
+ /** The built-in header. Clicking its logo (the first two lines, after one column of padding) plays an easter egg. */
114
+ class BuiltInHeader extends ExpandableText {
115
+ onLogoClick;
116
+ handleMouse(event) {
117
+ if (event.type !== "click" || event.y > 1 || event.x < 1 || event.x > 4 || !this.onLogoClick)
118
+ return undefined;
119
+ this.onLogoClick(event.screenX - event.x + 1, event.screenY - event.y);
120
+ return { handled: true };
121
+ }
122
+ }
109
123
  function isCustomSessionEntry(item) {
110
124
  return "type" in item && item.type === "custom";
111
125
  }
@@ -175,6 +189,7 @@ function llamaCppPostLoginGuidance(actionLabel, loadedModelCount) {
175
189
  ? `${actionLabel}. No llama.cpp models are loaded. Use /llama to load a model, then /model to select it.`
176
190
  : `${actionLabel}. Use /model to select a loaded llama.cpp model, or /llama to manage models.`;
177
191
  }
192
+ const RADIUS_LOGIN_INTRO = "Radius is a service crafted for Pi by the builders of Pi, Earendil Works";
178
193
  const AUTH_TYPE_ORDER = { oauth: 0, api_key: 1 };
179
194
  function createFuzzyAutocompleteItems(items, prefix, getSearchText, toAutocompleteItem) {
180
195
  const filtered = fuzzyFilter(items, prefix, getSearchText);
@@ -197,18 +212,21 @@ function getLoginProviderCompletionOptions(providerOptions) {
197
212
  id: provider.id,
198
213
  name: provider.name,
199
214
  authTypes: [provider.authType],
215
+ subscription: provider.subscription,
200
216
  });
201
217
  }
202
218
  return Array.from(byId.values()).sort((a, b) => a.name.localeCompare(b.name));
203
219
  }
204
220
  function getLoginProviderSearchText(provider) {
205
221
  const authTypes = provider.authTypes
206
- .map((authType) => `${authType} ${formatAuthSelectorProviderType(authType)}`)
222
+ .map((authType) => `${authType} ${formatAuthSelectorProviderType(authType, provider.subscription)}`)
207
223
  .join(" ");
208
224
  return `${provider.id} ${provider.name} ${authTypes}`;
209
225
  }
210
226
  function formatLoginProviderCompletionDescription(provider) {
211
- const authTypes = provider.authTypes.map(formatAuthSelectorProviderType).join("/");
227
+ const authTypes = provider.authTypes
228
+ .map((authType) => formatAuthSelectorProviderType(authType, provider.subscription))
229
+ .join("/");
212
230
  return provider.name === provider.id ? authTypes : `${provider.name} · ${authTypes}`;
213
231
  }
214
232
  export class InteractiveMode {
@@ -635,7 +653,7 @@ export class InteractiveMode {
635
653
  this.registerSignalHandlers();
636
654
  // Load changelog (only show new entries, skip for resumed sessions)
637
655
  this.changelogMarkdown = this.getChangelogForDisplay();
638
- if (this.session.scopedModels.length > 0 && (this.options.verbose || !this.settingsManager.getQuietStartup())) {
656
+ if (this.session.scopedModels.length > 0 && this.shouldShowStartupDetails()) {
639
657
  const modelList = this.session.scopedModels
640
658
  .map((sm) => {
641
659
  const thinkingStr = sm.thinkingLevel ? `:${sm.thinkingLevel}` : "";
@@ -681,15 +699,21 @@ export class InteractiveMode {
681
699
  // Start the UI before initializing extensions so session_start handlers can use interactive dialogs
682
700
  this.ui.start();
683
701
  this.isInitialized = true;
702
+ this.ensurePngTranscoder();
684
703
  this.themeController.applyFromSettings();
685
704
  // The header and startup notices bake theme colors into their text, so build them once the terminal
686
705
  // reported its colors. This ends at the terminal's DA1 reply, or after 100 ms if it answers nothing.
687
706
  await this.themeController.waitForTerminalColors();
688
707
  // Add header with keybindings from config (unless silenced)
689
- if (this.options.verbose || !this.settingsManager.getQuietStartup()) {
708
+ if (this.shouldShowStartupHeader()) {
709
+ const showDetails = this.shouldShowStartupDetails();
690
710
  // Built on demand so the header follows theme changes. The logo's first line carries the version,
691
- // its second line the first line of key hints.
711
+ // its second line the first line of key hints. Terminals that cannot render the logo get a
712
+ // "Pi vX" line instead, with the key hints below it.
713
+ const showLogo = supportsPiLogo();
692
714
  const withLogo = (hints) => {
715
+ if (!showLogo)
716
+ return `${piWordmark()} ${theme.fg("dim", `v${this.version}`)}\n${hints}`;
693
717
  const [top, bottom] = piLogoLines();
694
718
  return `${top} ${theme.fg("dim", `v${this.version}`)}\n${bottom} ${hints}`;
695
719
  };
@@ -723,9 +747,12 @@ export class InteractiveMode {
723
747
  rawKeyHint("!", "bash"),
724
748
  hint("app.tools.expand", "more"),
725
749
  ].join(theme.fg("muted", " · "));
726
- const compactOnboarding = () => theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help and loaded resources.`);
750
+ const compactOnboarding = () => theme.fg("dim", `Press ${keyText("app.tools.expand")} to show full startup help${showDetails ? " and loaded resources" : ""}.`);
727
751
  const onboarding = () => theme.fg("dim", `Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.`);
728
- this.builtInHeader = new ExpandableText(() => `${withLogo(compactInstructions())}\n${compactOnboarding()}\n\n${onboarding()}`, () => `${withLogo(expandedInstructions())}\n\n${onboarding()}`, this.getStartupExpansionState(), 1, 0);
752
+ const header = new BuiltInHeader(() => `${withLogo(compactInstructions())}\n${compactOnboarding()}\n\n${onboarding()}`, () => `${withLogo(expandedInstructions())}\n\n${onboarding()}`, this.getStartupExpansionState(), 1, 0);
753
+ if (showLogo)
754
+ header.onLogoClick = (column, row) => playPiLogo3d(this.renderer, column, row);
755
+ this.builtInHeader = header;
729
756
  // Setup UI layout
730
757
  this.headerContainer.addChild(new Spacer(1));
731
758
  this.headerContainer.addChild(this.builtInHeader);
@@ -1024,6 +1051,14 @@ export class InteractiveMode {
1024
1051
  getStartupExpansionState() {
1025
1052
  return this.options.verbose || this.toolOutputExpanded;
1026
1053
  }
1054
+ /** Startup header (logo, version, key hints). Hidden only by quietStartup: true. */
1055
+ shouldShowStartupHeader() {
1056
+ return this.options.verbose === true || this.settingsManager.getQuietStartup() !== true;
1057
+ }
1058
+ /** Startup details (model scope, loaded resources). Hidden by quietStartup: true or "header". */
1059
+ shouldShowStartupDetails() {
1060
+ return this.options.verbose === true || this.settingsManager.getQuietStartup() === false;
1061
+ }
1027
1062
  /**
1028
1063
  * Get a short path relative to the package root for display.
1029
1064
  */
@@ -1285,7 +1320,7 @@ export class InteractiveMode {
1285
1320
  showLoadedResources(options) {
1286
1321
  // Resource rendering is idempotent; chat clears no longer clear this separate container.
1287
1322
  this.loadedResourcesContainer.clear();
1288
- const showListing = options?.force || this.options.verbose || !this.settingsManager.getQuietStartup();
1323
+ const showListing = options?.force || this.shouldShowStartupDetails();
1289
1324
  const showDiagnostics = showListing || options?.showDiagnosticsWhenQuiet === true;
1290
1325
  if (!showListing && !showDiagnostics) {
1291
1326
  return;
@@ -1507,8 +1542,16 @@ export class InteractiveMode {
1507
1542
  applyFullscreenScrollbarSetting() {
1508
1543
  this.transcriptScrollView?.setScrollbar(this.settingsManager.getFullscreenScrollbar());
1509
1544
  }
1545
+ /** Lets extension images use the PNG transcoder; tool results register it themselves. */
1546
+ ensurePngTranscoder() {
1547
+ ensurePngTranscoder(() => {
1548
+ this.ui.invalidate();
1549
+ this.ui.requestRender();
1550
+ });
1551
+ }
1510
1552
  applyRuntimeSettings() {
1511
1553
  setCapabilityOverrides(this.settingsManager.getTerminalCapabilityOverrides());
1554
+ this.ensurePngTranscoder();
1512
1555
  configureHttpDispatcher(this.settingsManager.getHttpIdleTimeoutMs());
1513
1556
  this.applyFullscreenScrollbarSetting();
1514
1557
  if (this.renderer instanceof TuiAltScreen) {
@@ -1628,7 +1671,7 @@ export class InteractiveMode {
1628
1671
  * whatever this returns, so they never reach into the tool registry themselves.
1629
1672
  */
1630
1673
  getRegisteredToolDefinition(toolName) {
1631
- return withBuiltInRenderers(toolName, this.session.getToolDefinition(toolName));
1674
+ return this.session.extensionRunner.resolveToolRenderers(toolName, () => withBuiltInRenderers(toolName, this.session.getToolDefinition(toolName)));
1632
1675
  }
1633
1676
  getMarkdownTransformers() {
1634
1677
  return [this.mermaidMarkdownTransformer, ...this.session.extensionRunner.getMarkdownTransformers()];
@@ -4075,8 +4118,8 @@ export class InteractiveMode {
4075
4118
  onEnableInstallTelemetryChange: (enabled) => {
4076
4119
  this.settingsManager.setEnableInstallTelemetry(enabled);
4077
4120
  },
4078
- onQuietStartupChange: (enabled) => {
4079
- this.settingsManager.setQuietStartup(enabled);
4121
+ onQuietStartupChange: (quiet) => {
4122
+ this.settingsManager.setQuietStartup(quiet);
4080
4123
  },
4081
4124
  onDefaultProjectTrustChange: (defaultProjectTrust) => {
4082
4125
  this.settingsManager.setDefaultProjectTrust(defaultProjectTrust);
@@ -4224,7 +4267,6 @@ export class InteractiveMode {
4224
4267
  this.updateEditorBorderColor();
4225
4268
  this.showStatus(`Model: ${model.id}`);
4226
4269
  void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
4227
- this.checkDaxnutsEasterEgg(model);
4228
4270
  }
4229
4271
  catch (error) {
4230
4272
  this.showError(error instanceof Error ? error.message : String(error));
@@ -4357,7 +4399,6 @@ export class InteractiveMode {
4357
4399
  done();
4358
4400
  this.showStatus(persist ? `Default model: ${model.provider}/${model.id}` : `Model: ${model.id}`);
4359
4401
  void this.maybeWarnAboutAnthropicSubscriptionAuth(model);
4360
- this.checkDaxnutsEasterEgg(model);
4361
4402
  }
4362
4403
  catch (error) {
4363
4404
  done();
@@ -4728,6 +4769,7 @@ export class InteractiveMode {
4728
4769
  source: authStatus.label ?? authStatus.source,
4729
4770
  }
4730
4771
  : undefined;
4772
+ const subscription = provider.auth.oauth?.isSubscription === true;
4731
4773
  if ((!authType || authType === "oauth") && provider.auth.oauth) {
4732
4774
  options.push({
4733
4775
  id: provider.id,
@@ -4735,6 +4777,7 @@ export class InteractiveMode {
4735
4777
  authType: "oauth",
4736
4778
  method: provider.auth.oauth,
4737
4779
  status,
4780
+ subscription,
4738
4781
  });
4739
4782
  }
4740
4783
  if ((!authType || authType === "api_key") && provider.auth.apiKey) {
@@ -4744,6 +4787,7 @@ export class InteractiveMode {
4744
4787
  authType: "api_key",
4745
4788
  method: provider.auth.apiKey,
4746
4789
  status,
4790
+ subscription,
4747
4791
  });
4748
4792
  }
4749
4793
  }
@@ -4751,12 +4795,16 @@ export class InteractiveMode {
4751
4795
  }
4752
4796
  async getLogoutProviderOptions() {
4753
4797
  return (await this.session.modelRuntime.listCredentials({ signal: AbortSignal.timeout(15_000) }))
4754
- .map(({ providerId, type }) => ({
4755
- id: providerId,
4756
- name: this.session.modelRuntime.getProvider(providerId)?.name ?? providerId,
4757
- authType: type,
4758
- status: { type, source: "stored credential" },
4759
- }))
4798
+ .map(({ providerId, type }) => {
4799
+ const provider = this.session.modelRuntime.getProvider(providerId);
4800
+ return {
4801
+ id: providerId,
4802
+ name: provider?.name ?? providerId,
4803
+ authType: type,
4804
+ status: { type, source: "stored credential" },
4805
+ subscription: provider?.auth.oauth?.isSubscription === true,
4806
+ };
4807
+ })
4760
4808
  .sort((a, b) => a.name.localeCompare(b.name));
4761
4809
  }
4762
4810
  findLoginProviderOptions(providerRef) {
@@ -4786,18 +4834,25 @@ export class InteractiveMode {
4786
4834
  }
4787
4835
  this.showLoginProviderSelector(undefined, providerRef);
4788
4836
  }
4789
- async startProviderLogin(providerOption) {
4837
+ /** `onBack` reopens the selector the login was started from when the user cancels it. */
4838
+ async startProviderLogin(providerOption, onBack) {
4790
4839
  if (providerOption.authType === "oauth") {
4791
- await this.showLoginDialog(providerOption.id, providerOption.name);
4840
+ await this.showLoginDialog(providerOption.id, providerOption.name, onBack);
4792
4841
  }
4793
4842
  else if (providerOption.method?.login) {
4794
- await this.showApiKeyLoginDialog(providerOption.id, providerOption.name);
4843
+ await this.showApiKeyLoginDialog(providerOption.id, providerOption.name, onBack);
4795
4844
  }
4796
4845
  else {
4797
- this.showAmbientAuthDialog(providerOption);
4846
+ this.showAmbientAuthDialog(providerOption, onBack);
4798
4847
  }
4799
4848
  }
4800
4849
  showLoginAuthTypeSelector(providerOptions) {
4850
+ // The top-level selector offers Radius directly, as its last option.
4851
+ const radiusOption = providerOptions
4852
+ ? undefined
4853
+ : this.getLoginProviderOptions("oauth").find((provider) => provider.id === RADIUS_PROVIDER_ID);
4854
+ const radiusText = radiusOption ? `Sign in with ${radiusOption.name}` : undefined;
4855
+ const radiusLabel = radiusOption ? `${radiusText}${formatAuthSelectorProviderStatus(radiusOption)}` : undefined;
4801
4856
  const oauthProvider = providerOptions?.find((provider) => provider.authType === "oauth");
4802
4857
  const oauthLoginLabel = oauthProvider?.method && "loginLabel" in oauthProvider.method ? oauthProvider.method.loginLabel : undefined;
4803
4858
  const subscriptionLabel = oauthLoginLabel ?? "Sign in with an account";
@@ -4812,6 +4867,8 @@ export class InteractiveMode {
4812
4867
  if (availableAuthTypes.has("api_key")) {
4813
4868
  options.push(apiKeyLabel);
4814
4869
  }
4870
+ if (radiusLabel)
4871
+ options.push(radiusLabel);
4815
4872
  if (options.length === 0) {
4816
4873
  this.showStatus("No login methods available.");
4817
4874
  return;
@@ -4827,29 +4884,37 @@ export class InteractiveMode {
4827
4884
  ? `Select authentication method for ${providerOptions[0].name}:`
4828
4885
  : "Select authentication method:";
4829
4886
  this.showSelector((done) => {
4830
- const selector = new ExtensionSelectorComponent(title, options, (option) => {
4887
+ const onSelect = (option) => {
4831
4888
  done();
4889
+ if (radiusOption && option === radiusLabel) {
4890
+ void this.startProviderLogin(radiusOption, () => this.showLoginAuthTypeSelector());
4891
+ return;
4892
+ }
4832
4893
  const authType = option === subscriptionLabel ? "oauth" : "api_key";
4833
4894
  if (providerOptions) {
4834
4895
  const providerOption = providerOptions.find((provider) => provider.authType === authType);
4835
4896
  if (providerOption) {
4836
- void this.startProviderLogin(providerOption);
4897
+ void this.startProviderLogin(providerOption, () => this.showLoginAuthTypeSelector(providerOptions));
4837
4898
  }
4838
4899
  return;
4839
4900
  }
4840
4901
  this.showLoginProviderSelector(authType);
4841
- }, () => {
4902
+ };
4903
+ const onCancel = () => {
4842
4904
  done();
4843
4905
  this.ui.requestRender();
4844
- });
4845
- return { component: selector, focus: selector };
4906
+ };
4907
+ const selector = radiusLabel && radiusText
4908
+ ? createLoginMenuSelector(this.ui, title, options, { label: radiusLabel, text: radiusText }, onSelect, onCancel)
4909
+ : new ExtensionSelectorComponent(title, options, onSelect, onCancel);
4910
+ return { component: selector, focus: selector, dispose: () => selector.dispose() };
4846
4911
  });
4847
4912
  }
4848
4913
  showLoginProviderSelector(authType, initialSearchInput) {
4849
4914
  const providerOptions = this.getLoginProviderOptions(authType);
4850
4915
  if (providerOptions.length === 0) {
4851
4916
  const message = authType === "oauth"
4852
- ? "No subscription providers available."
4917
+ ? "No account providers available."
4853
4918
  : authType === "api_key"
4854
4919
  ? "No API key providers available."
4855
4920
  : "No login providers available.";
@@ -4863,7 +4928,7 @@ export class InteractiveMode {
4863
4928
  if (!providerOption) {
4864
4929
  return;
4865
4930
  }
4866
- await this.startProviderLogin(providerOption);
4931
+ await this.startProviderLogin(providerOption, () => this.showLoginProviderSelector(authType, initialSearchInput));
4867
4932
  }, () => {
4868
4933
  done();
4869
4934
  if (authType) {
@@ -4975,7 +5040,6 @@ export class InteractiveMode {
4975
5040
  if (selectedModel) {
4976
5041
  this.showStatus(`${actionLabel}. Selected ${selectedModel.id}. Credentials saved to ${getAuthPath()}`);
4977
5042
  void this.maybeWarnAboutAnthropicSubscriptionAuth(selectedModel);
4978
- this.checkDaxnutsEasterEgg(selectedModel);
4979
5043
  }
4980
5044
  else {
4981
5045
  this.showStatus(`${actionLabel}. Credentials saved to ${getAuthPath()}`);
@@ -5017,21 +5081,24 @@ export class InteractiveMode {
5017
5081
  })
5018
5082
  .finally(() => clearTimeout(timeout));
5019
5083
  }
5020
- showAmbientAuthDialog(providerOption) {
5084
+ showAmbientAuthDialog(providerOption, onBack) {
5021
5085
  const restoreEditor = () => {
5022
5086
  this.editorContainer.clear();
5023
5087
  this.editorContainer.addChild(this.editor);
5024
5088
  this.ui.setFocus(this.editor);
5025
5089
  this.ui.requestRender();
5026
5090
  };
5027
- const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => restoreEditor(), providerOption.name, `${providerOption.name} setup`);
5091
+ const dialog = new LoginDialogComponent(this.ui, providerOption.id, () => {
5092
+ restoreEditor();
5093
+ onBack?.();
5094
+ }, providerOption.name, `${providerOption.name} setup`);
5028
5095
  dialog.showInfo(`${providerOption.method?.name ?? "Authentication"} is configured outside ${APP_NAME}.`, [], true);
5029
5096
  this.editorContainer.clear();
5030
5097
  this.editorContainer.addChild(dialog);
5031
5098
  this.ui.setFocus(dialog);
5032
5099
  this.ui.requestRender();
5033
5100
  }
5034
- async showApiKeyLoginDialog(providerId, providerName) {
5101
+ async showApiKeyLoginDialog(providerId, providerName, onBack) {
5035
5102
  const previousModel = this.session.model;
5036
5103
  const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => {
5037
5104
  // Completion handled below
@@ -5064,12 +5131,15 @@ export class InteractiveMode {
5064
5131
  if (error instanceof CredentialSynchronizationError) {
5065
5132
  this.showError(`Saved API key for ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
5066
5133
  }
5067
- else if (errorMsg !== "Login cancelled") {
5134
+ else if (errorMsg === "Login cancelled") {
5135
+ onBack?.();
5136
+ }
5137
+ else {
5068
5138
  this.showError(`Failed to save API key for ${providerName}: ${errorMsg}`);
5069
5139
  }
5070
5140
  }
5071
5141
  }
5072
- showAuthSelect(dialog, prompt) {
5142
+ showAuthSelect(dialog, prompt, providerId) {
5073
5143
  return new Promise((resolve, reject) => {
5074
5144
  const restoreDialog = () => {
5075
5145
  this.editorContainer.clear();
@@ -5088,17 +5158,17 @@ export class InteractiveMode {
5088
5158
  }, () => {
5089
5159
  restoreDialog();
5090
5160
  reject(new Error("Login cancelled"));
5091
- });
5161
+ }, { description: providerId === RADIUS_PROVIDER_ID ? RADIUS_LOGIN_INTRO : undefined });
5092
5162
  this.editorContainer.clear();
5093
5163
  this.editorContainer.addChild(selector);
5094
5164
  this.ui.setFocus(selector);
5095
5165
  this.ui.requestRender();
5096
5166
  });
5097
5167
  }
5098
- async showAuthPrompt(dialog, prompt) {
5168
+ async showAuthPrompt(dialog, prompt, providerId) {
5099
5169
  let response;
5100
5170
  if (prompt.type === "select") {
5101
- response = this.showAuthSelect(dialog, prompt);
5171
+ response = this.showAuthSelect(dialog, prompt, providerId);
5102
5172
  }
5103
5173
  else if (prompt.type === "manual_code") {
5104
5174
  response = dialog.showManualInput(prompt.message);
@@ -5142,11 +5212,11 @@ export class InteractiveMode {
5142
5212
  async loginProvider(dialog, providerId, method) {
5143
5213
  await this.session.modelRuntime.login(providerId, method, {
5144
5214
  signal: dialog.signal,
5145
- prompt: (prompt) => this.showAuthPrompt(dialog, prompt),
5215
+ prompt: (prompt) => this.showAuthPrompt(dialog, prompt, providerId),
5146
5216
  notify: (event) => this.notifyAuthDialog(dialog, event),
5147
5217
  }, { getDeviceId: () => this.settingsManager.getOrCreateDeviceId() });
5148
5218
  }
5149
- async showLoginDialog(providerId, providerName) {
5219
+ async showLoginDialog(providerId, providerName, onBack) {
5150
5220
  const previousModel = this.session.model;
5151
5221
  const dialog = new LoginDialogComponent(this.ui, providerId, (_success, _message) => { }, providerName);
5152
5222
  this.editorContainer.clear();
@@ -5163,6 +5233,8 @@ export class InteractiveMode {
5163
5233
  await this.loginProvider(dialog, providerId, "oauth");
5164
5234
  restoreEditor();
5165
5235
  await this.completeProviderAuthentication(providerId, providerName, "oauth", previousModel);
5236
+ if (providerId === RADIUS_PROVIDER_ID)
5237
+ this.offerRadiusMcpServer(providerId, providerName);
5166
5238
  }
5167
5239
  catch (error) {
5168
5240
  restoreEditor();
@@ -5170,11 +5242,58 @@ export class InteractiveMode {
5170
5242
  if (error instanceof CredentialSynchronizationError) {
5171
5243
  this.showError(`Logged in to ${providerName}, but local model state could not be synchronized: ${errorMsg}`);
5172
5244
  }
5173
- else if (errorMsg !== "Login cancelled") {
5245
+ else if (errorMsg === "Login cancelled") {
5246
+ onBack?.();
5247
+ }
5248
+ else {
5174
5249
  this.showError(`Failed to login to ${providerName}: ${errorMsg}`);
5175
5250
  }
5176
5251
  }
5177
5252
  }
5253
+ /**
5254
+ * Offer to point the Radius MCP server in the global mcp.json at the Radius login, adding the server
5255
+ * when missing. Nothing is asked when a global server already uses this login.
5256
+ */
5257
+ offerRadiusMcpServer(providerId, providerName) {
5258
+ const mcpPath = path.join(getAgentDir(), "mcp.json");
5259
+ const normalizeUrl = (url) => url.replace(/\/+$/u, "");
5260
+ const { servers } = loadMcpConfig({
5261
+ agentDir: getAgentDir(),
5262
+ cwd: this.sessionManager.getCwd(),
5263
+ projectTrusted: false,
5264
+ });
5265
+ const existing = servers.find((server) => "url" in server.config && normalizeUrl(server.config.url) === normalizeUrl(RADIUS_MCP_URL));
5266
+ if (existing && "url" in existing.config && existing.config.auth?.provider === providerId)
5267
+ return;
5268
+ let name = existing?.name ?? "radius";
5269
+ if (!existing && servers.some((server) => server.name === name))
5270
+ name = "radius-mcp";
5271
+ const config = existing && "url" in existing.config
5272
+ ? { ...existing.config, auth: { provider: providerId } }
5273
+ : { url: RADIUS_MCP_URL, auth: { provider: providerId } };
5274
+ // `auth` replaces the MCP OAuth sign-in.
5275
+ delete config.oauth;
5276
+ this.showSelector((done) => {
5277
+ const selector = new ExtensionSelectorComponent(`Configure ${providerName} MCP in ${mcpPath}?`, ["Yes", "No"], (option) => {
5278
+ done();
5279
+ if (option !== "Yes")
5280
+ return;
5281
+ try {
5282
+ addMcpServerConfig(mcpPath, name, config);
5283
+ }
5284
+ catch (error) {
5285
+ this.showError(`Could not update ${mcpPath}: ${error instanceof Error ? error.message : String(error)}`);
5286
+ return;
5287
+ }
5288
+ // The MCP extension reads mcp.json when the session starts.
5289
+ void this.handleReloadCommand();
5290
+ }, () => {
5291
+ done();
5292
+ this.ui.requestRender();
5293
+ });
5294
+ return { component: selector, focus: selector };
5295
+ });
5296
+ }
5178
5297
  // =========================================================================
5179
5298
  // Command handlers
5180
5299
  // =========================================================================
@@ -5667,6 +5786,8 @@ export class InteractiveMode {
5667
5786
  this.ui.requestRender();
5668
5787
  }
5669
5788
  handleArminSaysHi() {
5789
+ if (playArmin3d(this.renderer))
5790
+ return;
5670
5791
  this.chatContainer.addChild(new Spacer(1));
5671
5792
  this.chatContainer.addChild(new ArminComponent(this.ui));
5672
5793
  this.ui.requestRender();
@@ -5676,16 +5797,6 @@ export class InteractiveMode {
5676
5797
  this.chatContainer.addChild(new EarendilAnnouncementComponent());
5677
5798
  this.ui.requestRender();
5678
5799
  }
5679
- handleDaxnuts() {
5680
- this.chatContainer.addChild(new Spacer(1));
5681
- this.chatContainer.addChild(new DaxnutsComponent(this.ui));
5682
- this.ui.requestRender();
5683
- }
5684
- checkDaxnutsEasterEgg(model) {
5685
- if (model.provider === "opencode" && model.id.toLowerCase().includes("kimi-k2.5")) {
5686
- this.handleDaxnuts();
5687
- }
5688
- }
5689
5800
  async handleBashCommand(command, excludeFromContext = false) {
5690
5801
  const extensionRunner = this.session.extensionRunner;
5691
5802
  // Emit user_bash event to let extensions intercept