@selesai/code 0.11.0 → 0.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/dist/cli/startup-ui.js +2 -1
  3. package/dist/core/agent-session.d.ts +12 -1
  4. package/dist/core/agent-session.js +77 -9
  5. package/dist/core/compaction/branch-summarization.js +4 -3
  6. package/dist/core/compaction/compaction.d.ts +5 -0
  7. package/dist/core/compaction/compaction.js +19 -4
  8. package/dist/core/extensions/index.d.ts +1 -1
  9. package/dist/core/extensions/runner.d.ts +5 -0
  10. package/dist/core/extensions/runner.js +48 -1
  11. package/dist/core/extensions/types.d.ts +18 -1
  12. package/dist/core/llama/client.d.ts +6 -0
  13. package/dist/core/llama/client.js +7 -0
  14. package/dist/core/llama/provider.d.ts +3 -1
  15. package/dist/core/llama/provider.js +28 -6
  16. package/dist/core/session-manager.js +4 -2
  17. package/dist/core/settings-manager.d.ts +8 -1
  18. package/dist/core/settings-manager.js +17 -0
  19. package/dist/defaults/models.json +32 -9
  20. package/dist/extensions/tokenin-onboarding.ts +112 -2
  21. package/dist/index.d.ts +2 -1
  22. package/dist/index.js +1 -0
  23. package/dist/main.js +2 -0
  24. package/dist/modes/interactive/components/settings-selector.d.ts +2 -0
  25. package/dist/modes/interactive/components/settings-selector.js +10 -0
  26. package/dist/modes/interactive/interactive-mode.d.ts +4 -0
  27. package/dist/modes/interactive/interactive-mode.js +55 -39
  28. package/dist/modes/rpc/rpc-client.d.ts +7 -0
  29. package/dist/modes/rpc/rpc-client.js +7 -0
  30. package/dist/modes/rpc/rpc-mode.js +3 -0
  31. package/dist/modes/rpc/rpc-types.d.ts +12 -0
  32. package/dist/utils/shell.js +6 -4
  33. package/docs/settings.md +4 -0
  34. package/package.json +4 -4
@@ -1,6 +1,6 @@
1
1
  import type { ThinkingLevel } from "@earendil-works/pi-agent-core";
2
2
  import type { Transport } from "@earendil-works/pi-ai";
3
- import type { ScrollViewScrollbar, TuiMode as RendererTuiMode } from "@earendil-works/pi-tui";
3
+ import type { ScrollViewScrollbar, TerminalCapabilities, TuiMode as RendererTuiMode } from "@earendil-works/pi-tui";
4
4
  export interface CompactionSettings {
5
5
  enabled?: boolean;
6
6
  reserveTokens?: number;
@@ -28,6 +28,9 @@ export interface TerminalSettings {
28
28
  imageWidthCells?: number;
29
29
  clearOnShrink?: boolean;
30
30
  showTerminalProgress?: boolean;
31
+ hyperlinks?: boolean | "auto";
32
+ images?: "kitty" | "iterm2" | "auto" | false;
33
+ trueColor?: boolean | "auto";
31
34
  }
32
35
  export interface ImageSettings {
33
36
  autoResize?: boolean;
@@ -104,6 +107,7 @@ export interface Settings {
104
107
  defaultTools?: string[];
105
108
  fullscreenExitOutput?: FullscreenExitOutput;
106
109
  fullscreenScrollbar?: ScrollViewScrollbar;
110
+ fullscreenCopyOnSelect?: boolean;
107
111
  images?: ImageSettings;
108
112
  enabledModels?: string[];
109
113
  doubleEscapeAction?: "fork" | "tree" | "none";
@@ -285,6 +289,7 @@ export declare class SettingsManager {
285
289
  setThemePaths(paths: string[]): void;
286
290
  setProjectThemePaths(paths: string[]): void;
287
291
  getThinkingBudgets(): ThinkingBudgetsSettings | undefined;
292
+ getTerminalCapabilityOverrides(): Partial<TerminalCapabilities>;
288
293
  getShowImages(): boolean;
289
294
  setShowImages(show: boolean): void;
290
295
  getImageWidthCells(): number;
@@ -299,6 +304,8 @@ export declare class SettingsManager {
299
304
  setFullscreenScrollbar(mode: ScrollViewScrollbar): void;
300
305
  getFullscreenExitOutput(): FullscreenExitOutput;
301
306
  setFullscreenExitOutput(output: FullscreenExitOutput): void;
307
+ getFullscreenCopyOnSelect(): boolean;
308
+ setFullscreenCopyOnSelect(enabled: boolean): void;
302
309
  getImageAutoResize(): boolean;
303
310
  setImageAutoResize(enabled: boolean): void;
304
311
  getBlockImages(): boolean;
@@ -786,6 +786,15 @@ export class SettingsManager {
786
786
  getThinkingBudgets() {
787
787
  return this.settings.thinkingBudgets;
788
788
  }
789
+ getTerminalCapabilityOverrides() {
790
+ const terminal = this.settings.terminal;
791
+ const images = terminal?.images;
792
+ return {
793
+ ...(images === "kitty" || images === "iterm2" ? { images } : images === false ? { images: null } : {}),
794
+ ...(typeof terminal?.trueColor === "boolean" ? { trueColor: terminal.trueColor } : {}),
795
+ ...(typeof terminal?.hyperlinks === "boolean" ? { hyperlinks: terminal.hyperlinks } : {}),
796
+ };
797
+ }
789
798
  getShowImages() {
790
799
  return this.settings.terminal?.showImages ?? true;
791
800
  }
@@ -864,6 +873,14 @@ export class SettingsManager {
864
873
  this.markModified("fullscreenExitOutput");
865
874
  this.save();
866
875
  }
876
+ getFullscreenCopyOnSelect() {
877
+ return this.settings.fullscreenCopyOnSelect ?? true;
878
+ }
879
+ setFullscreenCopyOnSelect(enabled) {
880
+ this.globalSettings.fullscreenCopyOnSelect = enabled;
881
+ this.markModified("fullscreenCopyOnSelect");
882
+ this.save();
883
+ }
867
884
  getImageAutoResize() {
868
885
  return this.settings.images?.autoResize ?? true;
869
886
  }
@@ -24,7 +24,10 @@
24
24
  "high": "high",
25
25
  "xhigh": null,
26
26
  "max": "max"
27
- }
27
+ },
28
+ "compat": {
29
+ "requiresReasoningContentOnAssistantMessages": true
30
+ }
28
31
  },
29
32
  {
30
33
  "id": "celestial-max",
@@ -40,7 +43,10 @@
40
43
  "high": "high",
41
44
  "xhigh": null,
42
45
  "max": "max"
43
- }
46
+ },
47
+ "compat": {
48
+ "requiresReasoningContentOnAssistantMessages": true
49
+ }
44
50
  },
45
51
  {
46
52
  "id": "celestial-ultra",
@@ -56,7 +62,10 @@
56
62
  "high": "high",
57
63
  "xhigh": null,
58
64
  "max": "max"
59
- }
65
+ },
66
+ "compat": {
67
+ "requiresReasoningContentOnAssistantMessages": true
68
+ }
60
69
  },
61
70
  {
62
71
  "id": "deepseek-v4-flash",
@@ -72,7 +81,10 @@
72
81
  "high": "high",
73
82
  "xhigh": null,
74
83
  "max": "max"
75
- }
84
+ },
85
+ "compat": {
86
+ "requiresReasoningContentOnAssistantMessages": true
87
+ }
76
88
  },
77
89
  {
78
90
  "id": "deepseek-v4-flash-vision-exp",
@@ -88,7 +100,10 @@
88
100
  "high": "high",
89
101
  "xhigh": null,
90
102
  "max": "max"
91
- }
103
+ },
104
+ "compat": {
105
+ "requiresReasoningContentOnAssistantMessages": true
106
+ }
92
107
  },
93
108
  {
94
109
  "id": "deepseek-v4-pro",
@@ -104,7 +119,10 @@
104
119
  "high": "high",
105
120
  "xhigh": null,
106
121
  "max": "max"
107
- }
122
+ },
123
+ "compat": {
124
+ "requiresReasoningContentOnAssistantMessages": true
125
+ }
108
126
  },
109
127
  {
110
128
  "id": "gpt-5.6-luna",
@@ -200,7 +218,10 @@
200
218
  "reasoning": false,
201
219
  "input": ["text", "image"],
202
220
  "contextWindow": 256000,
203
- "maxTokens": 64000
221
+ "maxTokens": 64000,
222
+ "compat": {
223
+ "thinkingFormat": "qwen"
224
+ }
204
225
  },
205
226
  {
206
227
  "id": "gemini-3.7-flash",
@@ -219,7 +240,8 @@
219
240
  "maxTokens": 8192,
220
241
  "compat": {
221
242
  "supportsDeveloperRole": false,
222
- "supportsReasoningEffort": false
243
+ "supportsReasoningEffort": false,
244
+ "thinkingFormat": "qwen"
223
245
  }
224
246
  },
225
247
  {
@@ -231,7 +253,8 @@
231
253
  "maxTokens": 8192,
232
254
  "compat": {
233
255
  "supportsDeveloperRole": false,
234
- "supportsReasoningEffort": false
256
+ "supportsReasoningEffort": false,
257
+ "thinkingFormat": "qwen"
235
258
  }
236
259
  }
237
260
  ]
@@ -16,6 +16,7 @@ import type { AuthStorage, ExtensionAPI, ExtensionCommandContext, ExtensionConte
16
16
 
17
17
  export const TOKEN_IN_PROVIDER = "tokenin";
18
18
  export const TOKEN_IN_DASHBOARD_URL = "https://token.selesai.in/dashboard/tokens";
19
+ export const TOKEN_IN_DEFAULT_BASE_URL = "https://lite.andlet.me/v1";
19
20
  export const ONBOARDING_MARKER_NAME = ".tokenInOnboardingComplete";
20
21
  export const PLACEHOLDER_API_KEY = "sk-xxx";
21
22
 
@@ -239,6 +240,93 @@ async function promptForToken(ctx: ExtensionContext): Promise<string | undefined
239
240
  return ctx.ui.input("Paste your Token-In token", "sk-...");
240
241
  }
241
242
 
243
+ // ---------------------------------------------------------------------------
244
+ // /tokenin usage — LiteLLM /key/info quota lookup
245
+ // ---------------------------------------------------------------------------
246
+
247
+ export interface TokenInUsage {
248
+ spend: number;
249
+ maxBudget?: number;
250
+ budgetResetAt?: string;
251
+ }
252
+
253
+ /** Parse a LiteLLM /key/info payload into spend/budget/reset. Exported for tests. */
254
+ export function parseTokenInUsage(payload: unknown): TokenInUsage | undefined {
255
+ if (!payload || typeof payload !== "object" || Array.isArray(payload)) return undefined;
256
+ const obj = payload as Record<string, unknown>;
257
+ const info =
258
+ obj.info && typeof obj.info === "object" && !Array.isArray(obj.info)
259
+ ? (obj.info as Record<string, unknown>)
260
+ : obj;
261
+ const spend = Number(info.spend);
262
+ if (!Number.isFinite(spend) || spend < 0) return undefined;
263
+ const usage: TokenInUsage = { spend };
264
+ const maxBudget = Number(info.max_budget);
265
+ if (Number.isFinite(maxBudget) && maxBudget > 0) usage.maxBudget = maxBudget;
266
+ if (typeof info.budget_reset_at === "string") usage.budgetResetAt = info.budget_reset_at;
267
+ return usage;
268
+ }
269
+
270
+ /** Format usage as a boxed multi-line display with a progress bar. Exported for tests. */
271
+ export function formatTokenInUsage(usage: TokenInUsage): string {
272
+ const money = (v: number) => `$${v.toFixed(2)}`;
273
+ const pct = usage.maxBudget !== undefined ? (usage.spend / usage.maxBudget) * 100 : 0;
274
+ const barWidth = 40;
275
+ const filled = usage.maxBudget !== undefined ? Math.min(barWidth, Math.round((pct / 100) * barWidth)) : 0;
276
+ const bar = "█".repeat(filled) + "░".repeat(barWidth - filled);
277
+ const pctText = usage.maxBudget !== undefined ? `${pct.toFixed(1)}%` : "—";
278
+
279
+ let summary = `Spent ${money(usage.spend)}`;
280
+ if (usage.maxBudget !== undefined) {
281
+ summary += ` of ${money(usage.maxBudget)} budget (${pct.toFixed(1)}%)`;
282
+ } else {
283
+ summary += " (no budget limit set)";
284
+ }
285
+
286
+ let footer = usage.maxBudget !== undefined ? `${money(Math.max(0, usage.maxBudget - usage.spend))} remaining` : "";
287
+ if (usage.budgetResetAt) {
288
+ const d = new Date(usage.budgetResetAt);
289
+ if (!Number.isNaN(d.getTime())) {
290
+ const ms = d.getTime() - Date.now();
291
+ if (ms > 0) {
292
+ const days = Math.floor(ms / 86_400_000);
293
+ const hours = Math.floor((ms % 86_400_000) / 3_600_000);
294
+ const reset = days > 0 ? `${days}d ${hours}h` : `${Math.max(1, Math.ceil(ms / 3_600_000))}h`;
295
+ footer += footer ? ` · resets in ${reset}` : `resets in ${reset}`;
296
+ }
297
+ }
298
+ }
299
+
300
+ const W = 50; // total box width
301
+ const inner = W - 4; // content width between │ padding
302
+ const pad = (s: string) => s + " ".repeat(Math.max(0, inner - s.length));
303
+ const top = `╭─ Token-In usage ${"─".repeat(W - 19)}╮`;
304
+ const bottom = `╰${"─".repeat(W - 2)}╯`;
305
+ return [
306
+ top,
307
+ `│ ${pad(summary)} │`,
308
+ `│ ${bar} ${pctText.padStart(5)} │`,
309
+ `│ ${pad(footer)} │`,
310
+ bottom,
311
+ ].join("\n");
312
+ }
313
+
314
+ /** Query LiteLLM /key/info at the proxy root with the account key. Exported for tests. */
315
+ export async function fetchTokenInUsage(apiKey: string, baseUrl: string): Promise<TokenInUsage> {
316
+ const root = baseUrl.replace(/\/v1\/?$/, "");
317
+ const response = await fetch(`${root}/key/info`, {
318
+ headers: { Authorization: `Bearer ${apiKey}`, Accept: "application/json" },
319
+ signal: AbortSignal.timeout(5_000),
320
+ });
321
+ if (!response.ok) {
322
+ throw new Error(`Token-In usage endpoint returned HTTP ${response.status}`);
323
+ }
324
+ const payload: unknown = await response.json();
325
+ const usage = parseTokenInUsage(payload);
326
+ if (!usage) throw new Error("Token-In usage endpoint returned an unexpected payload");
327
+ return usage;
328
+ }
329
+
242
330
  // ---------------------------------------------------------------------------
243
331
  // /tokenin subcommand handlers
244
332
  // ---------------------------------------------------------------------------
@@ -327,6 +415,25 @@ async function handleTokenInSwitch(ctx: ExtensionCommandContext): Promise<void>
327
415
  ctx.ui.notify(`Switched to account ${account.label}.`, "info");
328
416
  }
329
417
 
418
+ async function handleTokenInUsage(ctx: ExtensionCommandContext): Promise<void> {
419
+ const apiKey = getStoredTokenInApiKey(ctx.modelRegistry.authStorage);
420
+ if (!apiKey) {
421
+ ctx.ui.notify("No active Token-In token. Use /tokenin add first.", "warning");
422
+ return;
423
+ }
424
+ const auth = readTokenInAuth();
425
+ const activeId = getActiveTokenInAccountId(ctx.modelRegistry.authStorage);
426
+ const account = auth.accounts.find((a) => a.id === activeId);
427
+ const baseUrl = account?.baseUrl ?? getTokenInBaseUrl(readModelsJson()) ?? TOKEN_IN_DEFAULT_BASE_URL;
428
+ try {
429
+ const usage = await fetchTokenInUsage(apiKey, baseUrl);
430
+ ctx.ui.notify(formatTokenInUsage(usage), "info");
431
+ } catch (err) {
432
+ const message = err instanceof Error ? err.message : String(err);
433
+ ctx.ui.notify(`Could not fetch Token-In usage: ${message}`, "warning");
434
+ }
435
+ }
436
+
330
437
  async function handleTokenInRemove(ctx: ExtensionCommandContext): Promise<void> {
331
438
  if (!ctx.hasUI) {
332
439
  ctx.ui.notify("This command requires interactive mode.", "warning");
@@ -441,12 +548,13 @@ export default function tokenInOnboardingExtension(pi: ExtensionAPI): void {
441
548
  });
442
549
 
443
550
  pi.registerCommand("tokenin", {
444
- description: "Manage TokenIN accounts: /tokenin add|switch|remove",
551
+ description: "Manage TokenIN accounts: /tokenin add|switch|remove|usage",
445
552
  getArgumentCompletions: (prefix: string) => {
446
553
  const subcommands = [
447
554
  { name: "add", description: "Add a TokenIN account" },
448
555
  { name: "switch", description: "Switch active account" },
449
556
  { name: "remove", description: "Remove a saved account" },
557
+ { name: "usage", description: "Show Token-IN quota usage" },
450
558
  ];
451
559
  const lowerPrefix = prefix.toLowerCase();
452
560
  const items = subcommands.filter((s) => s.name.toLowerCase().startsWith(lowerPrefix));
@@ -463,8 +571,10 @@ export default function tokenInOnboardingExtension(pi: ExtensionAPI): void {
463
571
  await handleTokenInSwitch(ctx);
464
572
  } else if (sub === "remove") {
465
573
  await handleTokenInRemove(ctx);
574
+ } else if (sub === "usage") {
575
+ await handleTokenInUsage(ctx);
466
576
  } else {
467
- ctx.ui.notify("Usage: /tokenin add|switch|remove", "info");
577
+ ctx.ui.notify("Usage: /tokenin add|switch|remove|usage", "info");
468
578
  }
469
579
  },
470
580
  });
package/dist/index.d.ts CHANGED
@@ -5,7 +5,7 @@ export { readStoredCredential } from "./core/auth-storage.ts";
5
5
  export { ensureTool, getToolPath, type ManagedTool } from "./utils/tools-manager.ts";
6
6
  export { type BranchPreparation, type BranchSummaryResult, type CollectEntriesResult, type CompactionResult, type CutPointResult, calculateContextTokens, collectEntriesForBranchSummary, compact, DEFAULT_COMPACTION_SETTINGS, estimateTokens, type FileOperations, findCutPoint, findTurnStartIndex, type GenerateBranchSummaryOptions, generateBranchSummary, generateSummary, generateSummaryWithUsage, getLastAssistantUsage, prepareBranchEntries, serializeConversation, shouldCompact, } from "./core/compaction/index.ts";
7
7
  export { createEventBus, type EventBus, type EventBusController } from "./core/event-bus.ts";
8
- export type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BuildSystemPromptOptions, CompactOptions, ContextEvent, ContextUsage, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRenderOptions, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, GrepToolCallEvent, InlineExtension, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MessageEndEvent, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, SessionBeforeCompactEvent, SessionBeforeForkEvent, SessionBeforeSwitchEvent, SessionBeforeTreeEvent, SessionCompactEvent, SessionInfoChangedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, TerminalInputHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, TurnEndEvent, TurnStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
8
+ export type { AgentEndEvent, AgentSettledEvent, AgentStartEvent, AgentToolResult, AgentToolUpdateCallback, AppKeybinding, AutocompleteProviderFactory, BashToolCallEvent, BeforeAgentStartEvent, BeforeAgentStartEventResult, BeforeProviderHeadersEvent, BeforeProviderRequestEvent, BeforeProviderRequestEventResult, BuildSystemPromptOptions, CompactOptions, ContextEvent, ContextUsage, CustomToolCallEvent, EditToolCallEvent, EntryRenderer, EntryRenderOptions, ExecOptions, ExecResult, Extension, ExtensionActions, ExtensionAPI, ExtensionCommandContext, ExtensionCommandContextActions, ExtensionContext, ExtensionContextActions, ExtensionError, ExtensionEvent, ExtensionFactory, ExtensionFlag, ExtensionHandler, ExtensionRuntime, ExtensionShortcut, ExtensionUIContext, ExtensionUIDialogOptions, ExtensionWidgetOptions, FindToolCallEvent, GrepToolCallEvent, InlineExtension, InputEvent, InputEventResult, InputSource, KeybindingsManager, LoadExtensionsResult, LsToolCallEvent, MessageEndEvent, MessageRenderer, MessageRenderOptions, MessageStartEvent, MessageUpdateEvent, ProjectTrustContext, ProjectTrustEvent, ProjectTrustEventDecision, ProjectTrustEventResult, ProjectTrustHandler, ProviderConfig, ProviderModelConfig, ReadToolCallEvent, RegisteredCommand, RegisteredTool, ResolvedCommand, SessionBeforeCompactEvent, SessionBeforeForkEvent, SessionBeforeSwitchEvent, SessionBeforeTreeEvent, SessionCompactEvent, SessionInfoChangedEvent, SessionShutdownEvent, SessionStartEvent, SessionTreeEvent, SlashCommandInfo, SlashCommandSource, SourceInfo, TerminalInputHandler, ToolCallEvent, ToolCallEventResult, ToolDefinition, ToolExecutionEndEvent, ToolExecutionMode, ToolExecutionStartEvent, ToolExecutionUpdateEvent, ToolInfo, ToolRenderResultOptions, ToolResultEvent, TurnEndEvent, TurnStartEvent, UIPromptEndEvent, UIPromptKind, UIPromptStartEvent, UserBashEvent, UserBashEventResult, WidgetPlacement, WorkingIndicatorOptions, WriteToolCallEvent, } from "./core/extensions/index.ts";
9
9
  export { createExtensionRuntime, defineTool, discoverAndLoadExtensions, ExtensionRunner, isBashToolResult, isEditToolResult, isFindToolResult, isGrepToolResult, isLsToolResult, isReadToolResult, isToolCallEventType, isWriteToolResult, wrapRegisteredTool, wrapRegisteredTools, } from "./core/extensions/index.ts";
10
10
  export type { ReadonlyFooterDataProvider } from "./core/footer-data-provider.ts";
11
11
  export { convertToLlm } from "./core/messages.ts";
@@ -32,4 +32,5 @@ export { copyToClipboard } from "./utils/clipboard.ts";
32
32
  export { parseFrontmatter, stripFrontmatter } from "./utils/frontmatter.ts";
33
33
  export { convertToPng } from "./utils/image-convert.ts";
34
34
  export { formatDimensionNote, type ResizedImage, resizeImage } from "./utils/image-resize.ts";
35
+ export { detectSupportedImageMimeTypeFromFile } from "./utils/mime.ts";
35
36
  export { getShellConfig } from "./utils/shell.ts";
package/dist/index.js CHANGED
@@ -43,5 +43,6 @@ export { copyToClipboard } from "./utils/clipboard.js";
43
43
  export { parseFrontmatter, stripFrontmatter } from "./utils/frontmatter.js";
44
44
  export { convertToPng } from "./utils/image-convert.js";
45
45
  export { formatDimensionNote, resizeImage } from "./utils/image-resize.js";
46
+ export { detectSupportedImageMimeTypeFromFile } from "./utils/mime.js";
46
47
  // Shell utilities
47
48
  export { getShellConfig } from "./utils/shell.js";
package/dist/main.js CHANGED
@@ -6,6 +6,7 @@
6
6
  */
7
7
  import { createInterface } from "node:readline";
8
8
  import { modelsAreEqual } from "@earendil-works/pi-ai";
9
+ import { setCapabilityOverrides } from "@earendil-works/pi-tui";
9
10
  import chalk from "chalk";
10
11
  import { parseArgs, printHelp } from "./cli/args.js";
11
12
  import { CredentialPrintError, isCredentialPrintHelp, parseCredentialPrintCommand, printCredentialPrintHelp, resolveCredentialForPrint, validateCredentialPrintArgs, } from "./cli/credential-print.js";
@@ -659,6 +660,7 @@ export async function main(args, options) {
659
660
  time("createAgentSessionRuntime");
660
661
  const { services, session, modelFallbackMessage } = runtime;
661
662
  const { settingsManager, modelRuntime, resourceLoader } = services;
663
+ setCapabilityOverrides(settingsManager.getTerminalCapabilityOverrides());
662
664
  applyHttpProxySettings(settingsManager.getGlobalSettings().httpProxy);
663
665
  configureHttpDispatcher(settingsManager.getHttpIdleTimeoutMs());
664
666
  if (parsed.help) {
@@ -49,6 +49,7 @@ export interface SettingsConfig {
49
49
  tuiMode: TuiMode;
50
50
  fullscreenExitOutput: FullscreenExitOutput;
51
51
  fullscreenScrollbar: ScrollViewScrollbar;
52
+ fullscreenCopyOnSelect: boolean;
52
53
  warnings: WarningSettings;
53
54
  skills: SkillToggleItem[];
54
55
  }
@@ -86,6 +87,7 @@ export interface SettingsCallbacks {
86
87
  onTuiModeChange: (mode: TuiMode) => void;
87
88
  onFullscreenExitOutputChange: (output: FullscreenExitOutput) => void;
88
89
  onFullscreenScrollbarChange: (mode: ScrollViewScrollbar) => void;
90
+ onFullscreenCopyOnSelectChange: (enabled: boolean) => void;
89
91
  onWarningsChange: (warnings: WarningSettings) => void;
90
92
  onSkillsToggle: (item: SkillToggleItem, enabled: boolean) => void;
91
93
  onSkillsReload: () => void;
@@ -474,6 +474,13 @@ export class SettingsSelectorComponent extends Container {
474
474
  currentValue: config.fullscreenScrollbar,
475
475
  values: ["auto", "always", "hidden"],
476
476
  },
477
+ {
478
+ id: "fullscreen-copy-on-select",
479
+ label: "Fullscreen copy on select",
480
+ description: "Automatically copy selected text in fullscreen mode; disable to copy selections with Ctrl+X",
481
+ currentValue: config.fullscreenCopyOnSelect ? "true" : "false",
482
+ values: ["true", "false"],
483
+ },
477
484
  {
478
485
  id: "theme",
479
486
  label: "Theme",
@@ -697,6 +704,9 @@ export class SettingsSelectorComponent extends Container {
697
704
  case "fullscreen-scrollbar":
698
705
  callbacks.onFullscreenScrollbarChange(newValue);
699
706
  break;
707
+ case "fullscreen-copy-on-select":
708
+ callbacks.onFullscreenCopyOnSelectChange(newValue === "true");
709
+ break;
700
710
  case "theme":
701
711
  callbacks.onThemeChange(newValue);
702
712
  break;
@@ -43,6 +43,7 @@ interface InteractiveTuiOptions {
43
43
  logDirectory: string;
44
44
  terminal?: Terminal;
45
45
  onRightClickPaste?: () => void;
46
+ fullscreenCopyOnSelect?: boolean;
46
47
  }
47
48
  /** Composition root for selecting the interactive terminal renderer. */
48
49
  export declare function createInteractiveTui(options: InteractiveTuiOptions): TuiMainScreen | TuiAltScreen;
@@ -201,6 +202,7 @@ export declare class InteractiveMode {
201
202
  private setExtensionStatus;
202
203
  private showStatusIndicator;
203
204
  private clearStatusIndicator;
205
+ private showWorkingStatusIndicator;
204
206
  private setWorkingVisible;
205
207
  private setWorkingIndicator;
206
208
  private setHiddenThinkingLabel;
@@ -346,6 +348,8 @@ export declare class InteractiveMode {
346
348
  private cycleModel;
347
349
  private toggleToolOutputExpansion;
348
350
  private setToolsExpanded;
351
+ /** Update rendered assistant messages without rebuilding live tool components. */
352
+ private updateThinkingBlockVisibility;
349
353
  private toggleThinkingBlockVisibility;
350
354
  private handleOpenExternalEditor;
351
355
  clearEditor(): void;