@oh-my-pi/pi-coding-agent 9.6.4 → 9.8.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.
@@ -4,14 +4,14 @@
4
4
  import * as fs from "node:fs/promises";
5
5
  import * as os from "node:os";
6
6
  import * as path from "node:path";
7
- import { find as wasmFind } from "@oh-my-pi/pi-natives";
7
+ import { getSystemInfo as getNativeSystemInfo, type SystemInfo, find as wasmFind } from "@oh-my-pi/pi-natives";
8
8
  import { untilAborted } from "@oh-my-pi/pi-utils";
9
9
  import { $ } from "bun";
10
10
  import chalk from "chalk";
11
11
  import { contextFileCapability } from "./capability/context-file";
12
12
  import { systemPromptCapability } from "./capability/system-prompt";
13
13
  import { renderPromptTemplate } from "./config/prompt-templates";
14
- import { Settings, type SkillsSettings } from "./config/settings";
14
+ import type { SkillsSettings } from "./config/settings";
15
15
  import { type ContextFile, loadCapability, type SystemPrompt as SystemPromptFile } from "./discovery";
16
16
  import { loadSkills, type Skill } from "./extensibility/skills";
17
17
  import customSystemPromptTemplate from "./prompts/system/custom-system-prompt.md" with { type: "text" };
@@ -87,7 +87,7 @@ export async function loadGitContext(cwd: string): Promise<GitContext | null> {
87
87
  };
88
88
  }
89
89
 
90
- function firstNonEmpty(values: Array<string | undefined | null>): string | null {
90
+ function firstNonEmpty(...values: (string | undefined | null)[]): string | null {
91
91
  for (const value of values) {
92
92
  const trimmed = value?.trim();
93
93
  if (trimmed) return trimmed;
@@ -95,15 +95,6 @@ function firstNonEmpty(values: Array<string | undefined | null>): string | null
95
95
  return null;
96
96
  }
97
97
 
98
- function firstNonEmptyLine(value: string | null): string | null {
99
- if (!value) return null;
100
- const line = value
101
- .split("\n")
102
- .map(entry => entry.trim())
103
- .filter(Boolean)[0];
104
- return line ?? null;
105
- }
106
-
107
98
  function parseWmicTable(output: string, header: string): string | null {
108
99
  const lines = output
109
100
  .split("\n")
@@ -113,23 +104,6 @@ function parseWmicTable(output: string, header: string): string | null {
113
104
  return filtered[0] ?? null;
114
105
  }
115
106
 
116
- function parseKeyValueOutput(output: string): Record<string, string> {
117
- const result: Record<string, string> = {};
118
- for (const line of output.split("\n")) {
119
- const trimmed = line.trim();
120
- if (!trimmed) continue;
121
- const [key, ...rest] = trimmed.split("=");
122
- if (!key || rest.length === 0) continue;
123
- const value = rest.join("=").trim();
124
- if (value) result[key.trim()] = value;
125
- }
126
- return result;
127
- }
128
-
129
- function stripQuotes(value: string): string {
130
- return value.replace(/^"|"$/g, "");
131
- }
132
-
133
107
  const AGENTS_MD_PATTERN = "**/AGENTS.md";
134
108
  const AGENTS_MD_LIMIT = 200;
135
109
  const PROJECT_TREE_LIMIT = 2000;
@@ -382,147 +356,6 @@ async function buildProjectTreeSnapshot(root: string): Promise<string> {
382
356
  return renderProjectTree(scan, root);
383
357
  }
384
358
 
385
- function getOsName(): string {
386
- switch (process.platform) {
387
- case "win32":
388
- return "Windows";
389
- case "darwin":
390
- return "macOS";
391
- case "linux":
392
- return "Linux";
393
- case "freebsd":
394
- return "FreeBSD";
395
- case "openbsd":
396
- return "OpenBSD";
397
- case "netbsd":
398
- return "NetBSD";
399
- case "aix":
400
- return "AIX";
401
- default:
402
- return process.platform || "unknown";
403
- }
404
- }
405
-
406
- async function getKernelVersion(): Promise<string> {
407
- if (process.platform === "win32") {
408
- return await $`ver`
409
- .quiet()
410
- .text()
411
- .catch(() => "unknown");
412
- } else {
413
- return await $`uname -sr`
414
- .quiet()
415
- .text()
416
- .catch(() => "unknown");
417
- }
418
- }
419
-
420
- async function getOsDistro(): Promise<string | null> {
421
- switch (process.platform) {
422
- case "win32": {
423
- const output = await $`wmic os get Caption,Version /value`
424
- .quiet()
425
- .text()
426
- .catch(() => null);
427
- if (!output) return null;
428
- const parsed = parseKeyValueOutput(output);
429
- const caption = parsed.Caption;
430
- const version = parsed.Version;
431
- if (caption && version) return `${caption} ${version}`.trim();
432
- return caption ?? version ?? null;
433
- }
434
- case "darwin": {
435
- const name = firstNonEmptyLine(
436
- await $`sw_vers -productName`
437
- .quiet()
438
- .text()
439
- .catch(() => null),
440
- );
441
- const version = firstNonEmptyLine(
442
- await $`sw_vers -productVersion`
443
- .quiet()
444
- .text()
445
- .catch(() => null),
446
- );
447
- if (name && version) return `${name} ${version}`.trim();
448
- return name ?? version ?? null;
449
- }
450
- case "linux": {
451
- const lsb = firstNonEmptyLine(
452
- await $`lsb_release -ds`
453
- .quiet()
454
- .text()
455
- .catch(() => null),
456
- );
457
- if (lsb) return stripQuotes(lsb);
458
- const osRelease = await Bun.file("/etc/os-release")
459
- .text()
460
- .catch(() => null);
461
- if (!osRelease) return null;
462
- const parsed = parseKeyValueOutput(osRelease);
463
- const pretty = parsed.PRETTY_NAME ?? parsed.NAME;
464
- const version = parsed.VERSION ?? parsed.VERSION_ID;
465
- if (pretty) return stripQuotes(pretty);
466
- if (parsed.NAME && version) return `${stripQuotes(parsed.NAME)} ${stripQuotes(version)}`.trim();
467
- return parsed.NAME ? stripQuotes(parsed.NAME) : null;
468
- }
469
- default:
470
- return null;
471
- }
472
- }
473
-
474
- function getCpuArch(): string {
475
- return process.arch || "unknown";
476
- }
477
-
478
- async function getCpuModel(): Promise<string | null> {
479
- switch (process.platform) {
480
- case "win32": {
481
- const output = await $`wmic cpu get Name`
482
- .quiet()
483
- .text()
484
- .catch(() => null);
485
- return output ? parseWmicTable(output, "Name") : null;
486
- }
487
- case "darwin": {
488
- return firstNonEmptyLine(
489
- await $`sysctl -n machdep.cpu.brand_string`
490
- .quiet()
491
- .text()
492
- .catch(() => null),
493
- );
494
- }
495
- case "linux": {
496
- const lscpu = await $`lscpu`
497
- .quiet()
498
- .text()
499
- .catch(() => null);
500
- if (lscpu) {
501
- const match = lscpu
502
- .split("\n")
503
- .map(line => line.trim())
504
- .find(line => line.toLowerCase().startsWith("model name:"));
505
- if (match) return match.split(":").slice(1).join(":").trim();
506
- }
507
- const cpuInfo = await Bun.file("/proc/cpuinfo")
508
- .text()
509
- .catch(() => null);
510
- if (!cpuInfo) return null;
511
- for (const line of cpuInfo.split("\n")) {
512
- const [key, ...rest] = line.split(":");
513
- if (!key || rest.length === 0) continue;
514
- const normalized = key.trim().toLowerCase();
515
- if (normalized === "model name" || normalized === "hardware" || normalized === "processor") {
516
- return rest.join(":").trim();
517
- }
518
- }
519
- return null;
520
- }
521
- default:
522
- return null;
523
- }
524
- }
525
-
526
359
  async function getGpuModel(): Promise<string | null> {
527
360
  switch (process.platform) {
528
361
  case "win32": {
@@ -582,7 +415,7 @@ function getTerminalName(): string {
582
415
 
583
416
  if (process.env.WT_SESSION) return "Windows Terminal";
584
417
 
585
- const term = firstNonEmpty([process.env.TERM, process.env.COLORTERM, process.env.TERMINAL_EMULATOR]);
418
+ const term = firstNonEmpty(process.env.TERM, process.env.COLORTERM, process.env.TERMINAL_EMULATOR);
586
419
  return term ?? "unknown";
587
420
  }
588
421
 
@@ -598,12 +431,12 @@ function normalizeDesktopValue(value: string): string {
598
431
 
599
432
  function getDesktopEnvironment(): string {
600
433
  if (process.env.KDE_FULL_SESSION === "true") return "KDE";
601
- const raw = firstNonEmpty([
434
+ const raw = firstNonEmpty(
602
435
  process.env.XDG_CURRENT_DESKTOP,
603
436
  process.env.DESKTOP_SESSION,
604
437
  process.env.XDG_SESSION_DESKTOP,
605
438
  process.env.GDMSESSION,
606
- ]);
439
+ );
607
440
  return raw ? normalizeDesktopValue(raw) : "unknown";
608
441
  }
609
442
 
@@ -633,10 +466,10 @@ function matchKnownWindowManager(value: string): string | null {
633
466
  }
634
467
 
635
468
  function getWindowManager(): string {
636
- const explicit = firstNonEmpty([process.env.WINDOWMANAGER]);
469
+ const explicit = firstNonEmpty(process.env.WINDOWMANAGER);
637
470
  if (explicit) return explicit;
638
471
 
639
- const desktop = firstNonEmpty([process.env.XDG_CURRENT_DESKTOP, process.env.DESKTOP_SESSION]);
472
+ const desktop = firstNonEmpty(process.env.XDG_CURRENT_DESKTOP, process.env.DESKTOP_SESSION);
640
473
  if (desktop) {
641
474
  const matched = matchKnownWindowManager(desktop);
642
475
  if (matched) return matched;
@@ -682,76 +515,27 @@ async function saveSystemInfoCache(info: SystemInfoCache): Promise<void> {
682
515
  }
683
516
 
684
517
  async function collectSystemInfo(): Promise<SystemInfoCache> {
685
- const [distro, cpu, gpu, disk, kernel] = await Promise.all([
686
- getOsDistro(),
687
- getCpuModel(),
688
- getGpuModel(),
689
- getDiskInfo(),
690
- getKernelVersion(),
691
- ]);
518
+ let nativeInfo: SystemInfo | null = null;
519
+ try {
520
+ nativeInfo = getNativeSystemInfo();
521
+ } catch {
522
+ nativeInfo = null;
523
+ }
524
+
525
+ const gpu = await getGpuModel();
526
+ const cpus = os.cpus();
527
+
692
528
  return {
693
- os: getOsName(),
694
- distro: distro ?? "unknown",
695
- kernel: kernel ?? "unknown",
696
- arch: getCpuArch(),
697
- cpu: cpu ?? "unknown",
529
+ os: `${os.platform()} ${os.release()}`,
530
+ arch: os.arch(),
531
+ distro: nativeInfo?.distro ?? os.type(),
532
+ kernel: nativeInfo?.kernel ?? os.version(),
533
+ cpu: `${cpus.length}x ${nativeInfo?.cpu ?? cpus[0]?.model}`,
698
534
  gpu: gpu ?? "unknown",
699
- disk: disk ?? "unknown",
535
+ disk: nativeInfo?.disk ?? "unknown",
700
536
  };
701
537
  }
702
538
 
703
- function formatBytes(bytes: number): string {
704
- if (bytes < 1024) return `${bytes}B`;
705
- if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
706
- if (bytes < 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
707
- if (bytes < 1024 * 1024 * 1024 * 1024) return `${(bytes / (1024 * 1024 * 1024)).toFixed(1)}GB`;
708
- return `${(bytes / (1024 * 1024 * 1024 * 1024)).toFixed(1)}TB`;
709
- }
710
-
711
- async function getDiskInfo(): Promise<string | null> {
712
- switch (process.platform) {
713
- case "win32": {
714
- const output = await $`wmic logicaldisk get Caption,Size,FreeSpace /format:csv`
715
- .quiet()
716
- .text()
717
- .catch(() => null);
718
- if (!output) return null;
719
- const lines = output.split("\n").filter(l => l.trim() && !l.startsWith("Node"));
720
- const disks: string[] = [];
721
- for (const line of lines) {
722
- const parts = line.split(",");
723
- if (parts.length < 4) continue;
724
- const caption = parts[1]?.trim();
725
- const freeSpace = Number.parseInt(parts[2]?.trim() ?? "", 10);
726
- const size = Number.parseInt(parts[3]?.trim() ?? "", 10);
727
- if (!caption || Number.isNaN(size) || size === 0) continue;
728
- const used = size - (Number.isNaN(freeSpace) ? 0 : freeSpace);
729
- const pct = Math.round((used / size) * 100);
730
- disks.push(`${caption} ${formatBytes(used)}/${formatBytes(size)} (${pct}%)`);
731
- }
732
- return disks.length > 0 ? disks.join(", ") : null;
733
- }
734
- case "linux":
735
- case "darwin": {
736
- const output = await $`df -h /`
737
- .quiet()
738
- .text()
739
- .catch(() => null);
740
- if (!output) return null;
741
- const lines = output.split("\n");
742
- if (lines.length < 2) return null;
743
- const parts = lines[1].split(/\s+/);
744
- if (parts.length < 5) return null;
745
- const size = parts[1];
746
- const used = parts[2];
747
- const pct = parts[4];
748
- return `/ ${used}/${size} (${pct})`;
749
- }
750
- default:
751
- return null;
752
- }
753
- }
754
-
755
539
  async function getEnvironmentInfo(): Promise<Array<{ label: string; value: string }>> {
756
540
  // Load cached system info or collect fresh
757
541
  let sysInfo = await loadSystemInfoCache();
@@ -760,9 +544,6 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
760
544
  await saveSystemInfoCache(sysInfo);
761
545
  }
762
546
 
763
- // Get the actual shell used for command execution (not $SHELL)
764
- const shellConfig = (await Settings.init()).getShellConfig();
765
-
766
547
  return [
767
548
  { label: "OS", value: sysInfo.os },
768
549
  { label: "Distro", value: sysInfo.distro },
@@ -771,7 +552,6 @@ async function getEnvironmentInfo(): Promise<Array<{ label: string; value: strin
771
552
  { label: "CPU", value: sysInfo.cpu },
772
553
  { label: "GPU", value: sysInfo.gpu },
773
554
  { label: "Disk", value: sysInfo.disk },
774
- { label: "Shell", value: shellConfig.shell },
775
555
  { label: "Terminal", value: getTerminalName() },
776
556
  { label: "DE", value: getDesktopEnvironment() },
777
557
  { label: "WM", value: getWindowManager() },
package/src/tools/read.ts CHANGED
@@ -497,7 +497,6 @@ async function convertWithMarkitdown(
497
497
  allowNonZero: true,
498
498
  allowAbort: true,
499
499
  stderr: "buffer",
500
- detached: true,
501
500
  });
502
501
 
503
502
  if (result.exitError?.aborted) {
@@ -1,4 +1,4 @@
1
- import { PhotonImage } from "@oh-my-pi/pi-natives";
1
+ import { ImageFormat, PhotonImage } from "@oh-my-pi/pi-natives";
2
2
 
3
3
  /**
4
4
  * Convert image to PNG format for terminal display.
@@ -14,8 +14,8 @@ export async function convertToPng(
14
14
  }
15
15
 
16
16
  try {
17
- using image = await PhotonImage.new_from_byteslice(new Uint8Array(Buffer.from(base64Data, "base64")));
18
- const pngBuffer = await image.get_bytes();
17
+ const image = await PhotonImage.parse(new Uint8Array(Buffer.from(base64Data, "base64")));
18
+ const pngBuffer = await image.encode(ImageFormat.PNG, 100);
19
19
  return {
20
20
  data: Buffer.from(pngBuffer).toString("base64"),
21
21
  mimeType: "image/png",
@@ -1,5 +1,5 @@
1
1
  import type { ImageContent } from "@oh-my-pi/pi-ai";
2
- import { PhotonImage, resize, SamplingFilter } from "@oh-my-pi/pi-natives";
2
+ import { ImageFormat, PhotonImage, SamplingFilter } from "@oh-my-pi/pi-natives";
3
3
 
4
4
  export interface ImageResizeOptions {
5
5
  maxWidth?: number; // Default: 2000
@@ -54,10 +54,10 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
54
54
  const inputBuffer = Buffer.from(img.data, "base64");
55
55
 
56
56
  try {
57
- using image = await PhotonImage.new_from_byteslice(new Uint8Array(inputBuffer));
57
+ const image = await PhotonImage.parse(inputBuffer);
58
58
 
59
- const originalWidth = image.get_width();
60
- const originalHeight = image.get_height();
59
+ const originalWidth = image.width;
60
+ const originalHeight = image.height;
61
61
  const format = img.mimeType?.split("/")[1] ?? "png";
62
62
 
63
63
  // Check if already within all limits (dimensions AND size)
@@ -91,11 +91,14 @@ export async function resizeImage(img: ImageContent, options?: ImageResizeOption
91
91
  async function tryBothFormats(
92
92
  width: number,
93
93
  height: number,
94
- jpegQuality: number,
94
+ quality: number,
95
95
  ): Promise<{ buffer: Uint8Array; mimeType: string }> {
96
- using resized = await resize(image!, width, height, SamplingFilter.Lanczos3);
96
+ const resized = await image.resize(width, height, SamplingFilter.Lanczos3);
97
97
 
98
- const [pngBuffer, jpegBuffer] = await Promise.all([resized.get_bytes(), resized.get_bytes_jpeg(jpegQuality)]);
98
+ const [pngBuffer, jpegBuffer] = await Promise.all([
99
+ resized.encode(ImageFormat.PNG, quality),
100
+ resized.encode(ImageFormat.JPEG, quality),
101
+ ]);
99
102
 
100
103
  return pickSmaller(
101
104
  { buffer: pngBuffer, mimeType: "image/png" },
@@ -54,7 +54,6 @@ export async function convertWithMarkitdown(
54
54
  signal,
55
55
  allowNonZero: true,
56
56
  stderr: "full",
57
- detached: true,
58
57
  });
59
58
  if (!result.ok) {
60
59
  return {