@duckmind/dm-darwin-x64 0.33.8 → 0.34.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.
package/dm CHANGED
Binary file
@@ -51,6 +51,8 @@ export function isNativeEditorRule(line: string): boolean {
51
51
 
52
52
  export function splitNativeEditorRender(lines: readonly string[]): SplitEditorRenderResult {
53
53
  if (lines.length === 0) return { editorLines: [], popupLines: [] };
54
+ const boxed = splitNativeEditorBoxRender(lines);
55
+ if (boxed) return boxed;
54
56
  const withoutTop = isNativeEditorRule(lines[0] ?? "") ? lines.slice(1) : [...lines];
55
57
  const bottomRuleIndex = withoutTop.findIndex(isNativeEditorRule);
56
58
  if (bottomRuleIndex === -1) {
@@ -62,6 +64,38 @@ export function splitNativeEditorRender(lines: readonly string[]): SplitEditorRe
62
64
  };
63
65
  }
64
66
 
67
+ function splitNativeEditorBoxRender(lines: readonly string[]): SplitEditorRenderResult | null {
68
+ if (!isNativeEditorBoxTop(lines[0] ?? "")) return null;
69
+ const bottomIndex = lines.findIndex((line, index) => index > 0 && isNativeEditorBoxBottom(line));
70
+ if (bottomIndex === -1) return null;
71
+ return {
72
+ editorLines: lines.slice(1, bottomIndex).map(stripNativeEditorBoxContentLine),
73
+ popupLines: lines.slice(bottomIndex + 1),
74
+ };
75
+ }
76
+
77
+ function isNativeEditorBoxTop(line: string): boolean {
78
+ const plain = stripAnsi(line).trim();
79
+ return plain.startsWith("╭") && plain.endsWith("╮") && plain.includes("─");
80
+ }
81
+
82
+ function isNativeEditorBoxBottom(line: string): boolean {
83
+ const plain = stripAnsi(line).trim();
84
+ return plain.startsWith("╰") && plain.endsWith("╯") && plain.includes("─");
85
+ }
86
+
87
+ function isNativeEditorBoxContent(line: string): boolean {
88
+ const plain = stripAnsi(line);
89
+ return plain.startsWith("│") && plain.endsWith("│");
90
+ }
91
+
92
+ function stripNativeEditorBoxContentLine(line: string): string {
93
+ if (!isNativeEditorBoxContent(line)) return line;
94
+ return line
95
+ .replace(/^(?:\x1b\[[0-?]*[ -/]*[@-~])*│(?:\x1b\[[0-?]*[ -/]*[@-~])*/, "")
96
+ .replace(/(?:\x1b\[[0-?]*[ -/]*[@-~])*│(?:\x1b\[[0-?]*[ -/]*[@-~])*$/, "");
97
+ }
98
+
65
99
  export function renderBottomInputEditorLines(input: { lines: readonly string[]; width: number; theme: ThemeLike; state: BottomInputEditorState }): string[] {
66
100
  const width = Number.isFinite(input.width) ? Math.max(0, Math.floor(input.width)) : 0;
67
101
  if (!input.state.beautifiedInputEnabled || width < MIN_FRAME_WIDTH) return [...input.lines];
@@ -19,6 +19,14 @@ export type ContextUsage = {
19
19
  percent?: number;
20
20
  };
21
21
 
22
+ export type DuckmindCreditsSnapshot = {
23
+ usage: number | null;
24
+ remaining: number | null;
25
+ limit: number | null;
26
+ isFreeTier?: boolean | null;
27
+ fetchedAt?: number | null;
28
+ };
29
+
22
30
  export type BottomInputStatusState = {
23
31
  ctx: any;
24
32
  footerData?: any;
@@ -51,6 +59,7 @@ export type BottomInputStatusRender = {
51
59
  };
52
60
 
53
61
  export const CONTEXT_BAR_WIDTH = 10;
62
+ const CREDIT_BAR_WIDTH = 10;
54
63
  const CONTEXT_COLORS = {
55
64
  normal: "#00afaf",
56
65
  warning: "#febc38",
@@ -73,19 +82,26 @@ export function renderBottomInputStatus(input: BottomInputStatusState): BottomIn
73
82
  const safeWidth = Math.max(1, Math.floor(input.width));
74
83
  const enabled = input.beautifiedInputEnabled;
75
84
  const extensionStatuses = getVisibleExtensionStatuses(input.footerData);
85
+ const usage = readContextUsageSnapshot(input.ctx, input.isStreaming, input.liveUsage, input.latestAssistantUsage);
86
+ const lastPromptStatusLines = renderLastPromptStatusLines(input, usage, safeWidth);
76
87
  if (!enabled) {
77
88
  return {
78
89
  topLines: [],
79
90
  secondaryLines: renderExtensionStatusLines(extensionStatuses, safeWidth, input.theme),
80
- lastPromptLines: renderLastPromptLines(input.lastPrompt, safeWidth, input.theme),
91
+ lastPromptLines: [...lastPromptStatusLines, ...renderLastPromptLines(input.lastPrompt, safeWidth, input.theme)],
81
92
  frameStatus: emptyFrameStatus(),
82
- cacheKey: JSON.stringify({ width: safeWidth, enabled: false, lastPrompt: input.lastPrompt, extensionStatuses }),
93
+ cacheKey: JSON.stringify({
94
+ width: safeWidth,
95
+ enabled: false,
96
+ lastPrompt: input.lastPrompt,
97
+ extensionStatuses,
98
+ lastPromptStatusLines: lastPromptStatusLines.map(stripAnsi),
99
+ }),
83
100
  };
84
101
  }
85
102
 
86
103
  const icons = input.icons ?? getBottomInputIcons();
87
104
  const elapsedSeconds = Math.floor(Math.max(0, input.now - input.sessionStartTime) / 1000);
88
- const usage = readContextUsageSnapshot(input.ctx, input.isStreaming, input.liveUsage, input.latestAssistantUsage);
89
105
  const modelName = readModelName(input.ctx);
90
106
  const thinking = readThinkingLevel(input.ctx) ?? input.currentThinkingLevel ?? readThinkingLevelFromSession(input.ctx);
91
107
  const cacheKey = JSON.stringify({
@@ -97,13 +113,14 @@ export function renderBottomInputStatus(input: BottomInputStatusState): BottomIn
97
113
  elapsedSeconds,
98
114
  lastPrompt: input.lastPrompt,
99
115
  extensionStatuses,
116
+ lastPromptStatusLines: lastPromptStatusLines.map(stripAnsi),
100
117
  icons,
101
118
  });
102
119
 
103
120
  return {
104
121
  topLines: [],
105
122
  secondaryLines: renderExtensionStatusLines(extensionStatuses, safeWidth, input.theme),
106
- lastPromptLines: renderLastPromptLines(input.lastPrompt, safeWidth, input.theme),
123
+ lastPromptLines: [...lastPromptStatusLines, ...renderLastPromptLines(input.lastPrompt, safeWidth, input.theme)],
107
124
  frameStatus: renderFrameStatus({ ...input, width: safeWidth, icons }, modelName, thinking, usage),
108
125
  cacheKey,
109
126
  };
@@ -139,6 +156,15 @@ export function renderLastPromptLines(prompt: string, width: number, theme: Them
139
156
  return [truncateToWidth(`${prefix}${safeFg(theme, "muted", value)}`, safeWidth, "…", false)];
140
157
  }
141
158
 
159
+ export function renderLastPromptStatusLines(input: BottomInputStatusState, usage = readContextUsageSnapshot(input.ctx, input.isStreaming, input.liveUsage, input.latestAssistantUsage), width = input.width): string[] {
160
+ const safeWidth = Math.max(1, Math.floor(width));
161
+ if (safeWidth < 10) return [];
162
+
163
+ const statusLine = renderContextCostLine(input, usage, safeWidth);
164
+ const creditsLine = renderDuckmindCreditsLine(input, safeWidth);
165
+ return [statusLine, creditsLine].filter((line): line is string => typeof line === "string" && visibleWidth(stripAnsi(line)) > 0);
166
+ }
167
+
142
168
  export function getVisibleExtensionStatuses(footerData: any): string[] {
143
169
  let statuses: unknown;
144
170
  try {
@@ -236,6 +262,66 @@ function renderContextSegment(usage: ContextUsage | null): string | null {
236
262
  return applyHexColor(CONTEXT_COLORS.normal, formatTokens(usage.tokens));
237
263
  }
238
264
 
265
+ function renderContextCostLine(input: BottomInputStatusState, usage: ContextUsage | null, width: number): string {
266
+ const contextText = formatContextStatus(input.ctx, usage);
267
+ const costInfo = readModelCostInfo(input.ctx);
268
+ const costText = formatModelCostInline(costInfo);
269
+ const left = safeFg(input.theme, "muted", contextText);
270
+ const right = safeFg(input.theme, costInfo.color, costText, costInfo.color === "success" ? "accent" : "text");
271
+ return renderEdgeAlignedLine(left, right, width);
272
+ }
273
+
274
+ function renderDuckmindCreditsLine(input: BottomInputStatusState, width: number): string | null {
275
+ if (!isDuckmindProvider(input.ctx)) return null;
276
+ const credits = readDuckmindCredits(input.footerData);
277
+ if (!credits) return null;
278
+
279
+ const left = [
280
+ safeFg(input.theme, "muted", "Credits Remaining"),
281
+ renderCreditProgressBar(credits, input.theme),
282
+ safeFg(input.theme, "accent", formatCreditNumber(credits.remaining)),
283
+ ].join(" ");
284
+ const right = `${safeFg(input.theme, "muted", "Credits Used")} ${safeFg(input.theme, "accent", formatCreditNumber(credits.usage))} ${safeFg(input.theme, "muted", "• Credit")}`;
285
+ return renderEdgeAlignedLine(left, right, width);
286
+ }
287
+
288
+ function formatContextStatus(ctx: any, usage: ContextUsage | null): string {
289
+ const tokens = usage?.tokens ?? 0;
290
+ const contextWindow = usage?.contextWindow ?? readModelContextWindow(ctx);
291
+ const mode = `(${readContextManagementMode(ctx)})`;
292
+ if (contextWindow > 0) {
293
+ const usagePercent = usage?.percent;
294
+ const percent = typeof usagePercent === "number" && Number.isFinite(usagePercent)
295
+ ? usagePercent
296
+ : (tokens / contextWindow) * 100;
297
+ return `${Math.max(0, percent).toFixed(1)}%/${formatTokens(contextWindow)} ${mode}`;
298
+ }
299
+ return `${formatTokens(tokens)} ${mode}`;
300
+ }
301
+
302
+ function readContextManagementMode(ctx: any): string {
303
+ const candidates: unknown[] = [];
304
+ try {
305
+ candidates.push(ctx?.getAutoCompactionEnabled?.());
306
+ } catch {
307
+ // Ignore optional API probes; older runtimes do not expose this value.
308
+ }
309
+ try {
310
+ candidates.push(ctx?.autoCompactionEnabled, ctx?.autoCompactEnabled, ctx?.session?.autoCompactionEnabled, ctx?.session?.autoCompactEnabled);
311
+ } catch {
312
+ // Keep the display stable if the host shape changes.
313
+ }
314
+ for (const candidate of candidates) {
315
+ if (candidate === true) return "auto";
316
+ if (candidate === false) return "manual";
317
+ if (typeof candidate === "string") {
318
+ const normalized = candidate.trim().toLowerCase();
319
+ if (normalized === "auto" || normalized === "manual" || normalized === "off") return normalized;
320
+ }
321
+ }
322
+ return "auto";
323
+ }
324
+
239
325
  function renderElapsedSegment(theme: ThemeLike, startedAt: number, now: number, icons: BottomInputIconSet): string | null {
240
326
  const elapsed = Math.max(0, now - startedAt);
241
327
  if (elapsed < 1000) return null;
@@ -349,6 +435,135 @@ function readModelName(ctx: any): string | null {
349
435
  }
350
436
  }
351
437
 
438
+ type ModelCostInfo = {
439
+ label: string;
440
+ color: "success" | "muted" | "warning" | "error";
441
+ inputPerMillion: number | null;
442
+ outputPerMillion: number | null;
443
+ };
444
+
445
+ function readModelCostInfo(ctx: any): ModelCostInfo {
446
+ let model: any;
447
+ try {
448
+ model = ctx?.model;
449
+ } catch {
450
+ model = null;
451
+ }
452
+ if (model?.id === "@preset/free") return classifyModelCost(0, 0);
453
+ const input = toFiniteNumber(model?.cost?.input);
454
+ const output = toFiniteNumber(model?.cost?.output);
455
+ return classifyModelCost(input, output);
456
+ }
457
+
458
+ function classifyModelCost(inputPerMillion: number | null, outputPerMillion: number | null): ModelCostInfo {
459
+ if (inputPerMillion === null || outputPerMillion === null) {
460
+ return { label: "Unknown Cost", color: "muted", inputPerMillion: null, outputPerMillion: null };
461
+ }
462
+ if (inputPerMillion === 0 && outputPerMillion === 0) {
463
+ return { label: "Free", color: "success", inputPerMillion, outputPerMillion };
464
+ }
465
+ if (inputPerMillion > 20 || outputPerMillion > 100) {
466
+ return { label: "Critical Cost", color: "error", inputPerMillion, outputPerMillion };
467
+ }
468
+ if (inputPerMillion > 10 || outputPerMillion > 30) {
469
+ return { label: "High Cost", color: "error", inputPerMillion, outputPerMillion };
470
+ }
471
+ if ((inputPerMillion >= 1 && inputPerMillion <= 10) || (outputPerMillion >= 5 && outputPerMillion <= 30)) {
472
+ return { label: "Moderate Cost", color: "warning", inputPerMillion, outputPerMillion };
473
+ }
474
+ if (inputPerMillion < 1 && outputPerMillion < 5) {
475
+ return { label: "Low Cost", color: "success", inputPerMillion, outputPerMillion };
476
+ }
477
+ return { label: "Unknown Cost", color: "muted", inputPerMillion, outputPerMillion };
478
+ }
479
+
480
+ function formatModelCostInline(info: ModelCostInfo): string {
481
+ return `${info.label} ${formatUsd(info.inputPerMillion)}/M in · ${formatUsd(info.outputPerMillion)}/M out`;
482
+ }
483
+
484
+ function formatUsd(value: number | null): string {
485
+ if (value === null) return "n/a";
486
+ if (value === 0) return "$0";
487
+ if (value >= 100) return `$${value.toFixed(0)}`;
488
+ if (value >= 10) return `$${value.toFixed(1).replace(/\.0$/, "")}`;
489
+ if (value >= 1) return `$${value.toFixed(2).replace(/0$/, "").replace(/\.$/, "")}`;
490
+ return `$${value.toFixed(3).replace(/0+$/, "").replace(/\.$/, "")}`;
491
+ }
492
+
493
+ function readDuckmindCredits(footerData: any): DuckmindCreditsSnapshot | null {
494
+ let raw: unknown;
495
+ try {
496
+ raw = footerData?.getDuckmindCredits?.();
497
+ } catch {
498
+ return null;
499
+ }
500
+ if (!isRecord(raw)) return null;
501
+ const usage = toFiniteNumber(raw.usage);
502
+ const remaining = toFiniteNumber(raw.remaining);
503
+ const limit = toFiniteNumber(raw.limit);
504
+ if (usage === null && remaining === null && limit === null) return null;
505
+ return {
506
+ usage,
507
+ remaining,
508
+ limit,
509
+ isFreeTier: typeof raw.isFreeTier === "boolean" ? raw.isFreeTier : null,
510
+ fetchedAt: toFiniteNumber(raw.fetchedAt),
511
+ };
512
+ }
513
+
514
+ function renderCreditProgressBar(credits: DuckmindCreditsSnapshot, theme: ThemeLike): string {
515
+ const remaining = credits.remaining ?? 0;
516
+ const limit = credits.limit && credits.limit > 0 ? credits.limit : (remaining > 0 ? remaining : 0);
517
+ const percent = limit > 0 ? Math.max(0, Math.min(100, (remaining / limit) * 100)) : 0;
518
+ const filledCells = Math.round((percent / 100) * CREDIT_BAR_WIDTH);
519
+ const filled = "█".repeat(filledCells);
520
+ const empty = "░".repeat(Math.max(0, CREDIT_BAR_WIDTH - filledCells));
521
+ const color = percent <= 15 ? "error" : percent <= 35 ? "warning" : "success";
522
+ return `${safeFg(theme, "muted", "[")}${safeFg(theme, color, filled, "accent")}${safeFg(theme, "borderMuted", empty)}${safeFg(theme, "muted", "]")}`;
523
+ }
524
+
525
+ function isDuckmindProvider(ctx: any): boolean {
526
+ let values: unknown[] = [];
527
+ try {
528
+ const model = ctx?.model;
529
+ values = [
530
+ model?.provider,
531
+ model?.provider?.id,
532
+ model?.provider?.name,
533
+ model?.providerName,
534
+ model?.baseUrl,
535
+ model?.id,
536
+ ctx?.provider,
537
+ ctx?.provider?.id,
538
+ ctx?.provider?.name,
539
+ ctx?.provider?.baseUrl,
540
+ ];
541
+ } catch {
542
+ return false;
543
+ }
544
+ return values.some((value) => typeof value === "string" && /duckmind|openrouter|@preset\//i.test(value));
545
+ }
546
+
547
+ function renderEdgeAlignedLine(left: string, right: string, width: number): string {
548
+ const safeWidth = Math.max(1, Math.floor(width));
549
+ if (safeWidth <= 1) return truncateToWidth(left || right, safeWidth, "…", false);
550
+ const minGap = 2;
551
+ let rightPart = right;
552
+ let leftPart = left;
553
+ const originalLeftWidth = visibleWidth(leftPart);
554
+ const originalRightWidth = visibleWidth(rightPart);
555
+ if (originalLeftWidth + minGap + originalRightWidth > safeWidth) {
556
+ const rightBudget = Math.max(0, Math.min(originalRightWidth, Math.floor(safeWidth * 0.55)));
557
+ rightPart = rightBudget > 0 ? truncateToWidth(rightPart, rightBudget, "…", false) : "";
558
+ const leftBudget = Math.max(0, safeWidth - visibleWidth(rightPart) - (rightPart ? minGap : 0));
559
+ leftPart = leftBudget > 0 ? truncateToWidth(leftPart, leftBudget, "…", false) : "";
560
+ }
561
+ if (!leftPart) return truncateToWidth(rightPart, safeWidth, "…", false);
562
+ if (!rightPart) return truncateToWidth(leftPart, safeWidth, "…", false);
563
+ const gap = Math.max(1, safeWidth - visibleWidth(leftPart) - visibleWidth(rightPart));
564
+ return truncateToWidth(`${leftPart}${" ".repeat(gap)}${rightPart}`, safeWidth, "…", false);
565
+ }
566
+
352
567
  export function renderContextBar(percent: number, color = CONTEXT_COLORS.normal): string {
353
568
  const clamped = Math.max(0, Math.min(100, percent));
354
569
  const filledCells = Math.floor((clamped / 100) * CONTEXT_BAR_WIDTH);
@@ -382,6 +597,23 @@ function formatDuration(ms: number): string {
382
597
  return `${seconds}s`;
383
598
  }
384
599
 
600
+ function formatCreditNumber(value: number | null): string {
601
+ if (value === null || !Number.isFinite(value)) return "n/a";
602
+ const abs = Math.abs(value);
603
+ if (abs >= 1000) return value.toFixed(0);
604
+ if (abs >= 100) return value.toFixed(1).replace(/\.0$/, "");
605
+ return value.toFixed(2).replace(/0+$/, "").replace(/\.$/, "");
606
+ }
607
+
608
+ function toFiniteNumber(value: unknown): number | null {
609
+ if (typeof value === "number" && Number.isFinite(value)) return value;
610
+ if (typeof value === "string" && value.trim() !== "") {
611
+ const parsed = Number(value);
612
+ return Number.isFinite(parsed) ? parsed : null;
613
+ }
614
+ return null;
615
+ }
616
+
385
617
  function safeFg(theme: ThemeLike, token: string, text: string, fallback = "text"): string {
386
618
  try {
387
619
  return theme.fg(token, text);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@duckmind/dm-darwin-x64",
3
- "version": "0.33.8",
3
+ "version": "0.34.1",
4
4
  "description": "DuckMind (dm) binary payload for darwin x64",
5
5
  "license": "MIT",
6
6
  "os": [