pi-image-tools 1.2.0 → 1.3.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.
@@ -0,0 +1,77 @@
1
+ import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
2
+ import {
3
+ defaultCommandRunner,
4
+ MAX_BUFFER_BYTES,
5
+ READ_TIMEOUT_MS,
6
+ type CommandRunner,
7
+ } from "./command-runner.js";
8
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
9
+
10
+ const PUBLIC_PNG_SCRIPT = `ObjC.import('AppKit');
11
+ const pasteboard = $.NSPasteboard.generalPasteboard;
12
+ const data = pasteboard.dataForType('public.png');
13
+ if (!data) {
14
+ $.exit(2);
15
+ }
16
+ $.NSFileHandle.fileHandleWithStandardOutput.writeData(data);`;
17
+
18
+ export interface OsascriptPublicPngProviderOptions {
19
+ priority?: number;
20
+ commandRunner?: CommandRunner;
21
+ commandExists?: CommandExists;
22
+ }
23
+
24
+ export class OsascriptPublicPngProvider implements ClipboardImageProvider {
25
+ readonly capabilities;
26
+ private readonly commandRunner: CommandRunner;
27
+ private readonly commandExists: CommandExists;
28
+
29
+ constructor(options: OsascriptPublicPngProviderOptions = {}) {
30
+ this.capabilities = {
31
+ id: "mac-osascript-public-png",
32
+ name: "osascript public.png",
33
+ platforms: ["darwin"],
34
+ priority: options.priority ?? 20,
35
+ };
36
+ this.commandRunner = options.commandRunner ?? defaultCommandRunner;
37
+ this.commandExists = options.commandExists ?? defaultCommandExists;
38
+ }
39
+
40
+ isAvailable(context: ClipboardProviderContext): boolean {
41
+ try {
42
+ return this.commandExists("osascript", context);
43
+ } catch {
44
+ return false;
45
+ }
46
+ }
47
+
48
+ read(context: ClipboardProviderContext): ClipboardReadResult {
49
+ const wrapped = buildNamespaceWrappedCommand(
50
+ "osascript",
51
+ ["-l", "JavaScript", "-e", PUBLIC_PNG_SCRIPT],
52
+ context,
53
+ this.commandExists,
54
+ );
55
+ const result = this.commandRunner(wrapped.command, wrapped.args, {
56
+ environment: context.environment,
57
+ maxBuffer: MAX_BUFFER_BYTES,
58
+ timeout: READ_TIMEOUT_MS,
59
+ });
60
+
61
+ if (result.missingCommand) {
62
+ return { available: false, image: null };
63
+ }
64
+
65
+ if (!result.ok || result.stdout.length === 0) {
66
+ return { available: true, image: null };
67
+ }
68
+
69
+ return {
70
+ available: true,
71
+ image: {
72
+ bytes: new Uint8Array(result.stdout),
73
+ mimeType: "image/png",
74
+ },
75
+ };
76
+ }
77
+ }
@@ -0,0 +1,69 @@
1
+ import { buildNamespaceWrappedCommand, defaultCommandExists, type CommandExists } from "../shell-environment.js";
2
+ import {
3
+ defaultCommandRunner,
4
+ MAX_BUFFER_BYTES,
5
+ READ_TIMEOUT_MS,
6
+ type CommandRunner,
7
+ } from "./command-runner.js";
8
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
9
+
10
+ export interface PngpasteProviderOptions {
11
+ priority?: number;
12
+ commandRunner?: CommandRunner;
13
+ commandExists?: CommandExists;
14
+ }
15
+
16
+ export class PngpasteProvider implements ClipboardImageProvider {
17
+ readonly capabilities;
18
+ private readonly commandRunner: CommandRunner;
19
+ private readonly commandExists: CommandExists;
20
+
21
+ constructor(options: PngpasteProviderOptions = {}) {
22
+ this.capabilities = {
23
+ id: "mac-pngpaste",
24
+ name: "pngpaste",
25
+ platforms: ["darwin"],
26
+ priority: options.priority ?? 10,
27
+ };
28
+ this.commandRunner = options.commandRunner ?? defaultCommandRunner;
29
+ this.commandExists = options.commandExists ?? defaultCommandExists;
30
+ }
31
+
32
+ isAvailable(context: ClipboardProviderContext): boolean {
33
+ try {
34
+ return this.commandExists("pngpaste", context);
35
+ } catch {
36
+ return false;
37
+ }
38
+ }
39
+
40
+ read(context: ClipboardProviderContext): ClipboardReadResult {
41
+ const wrapped = buildNamespaceWrappedCommand(
42
+ "pngpaste",
43
+ ["-"],
44
+ context,
45
+ this.commandExists,
46
+ );
47
+ const result = this.commandRunner(wrapped.command, wrapped.args, {
48
+ environment: context.environment,
49
+ maxBuffer: MAX_BUFFER_BYTES,
50
+ timeout: READ_TIMEOUT_MS,
51
+ });
52
+
53
+ if (result.missingCommand) {
54
+ return { available: false, image: null };
55
+ }
56
+
57
+ if (!result.ok || result.stdout.length === 0) {
58
+ return { available: true, image: null };
59
+ }
60
+
61
+ return {
62
+ available: true,
63
+ image: {
64
+ bytes: new Uint8Array(result.stdout),
65
+ mimeType: "image/png",
66
+ },
67
+ };
68
+ }
69
+ }
@@ -0,0 +1,84 @@
1
+ import { createRequire } from "node:module";
2
+
3
+ import { hasGraphicalSession } from "../shell-environment.js";
4
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
5
+ import type { ClipboardModule } from "../types.js";
6
+
7
+ const require = createRequire(import.meta.url);
8
+ let cachedClipboardModule: ClipboardModule | null | undefined;
9
+
10
+ export type ClipboardModuleLoader = (
11
+ platform: NodeJS.Platform,
12
+ environment: NodeJS.ProcessEnv,
13
+ ) => ClipboardModule | null;
14
+
15
+ export function loadClipboardModule(
16
+ platform: NodeJS.Platform = process.platform,
17
+ environment: NodeJS.ProcessEnv = process.env,
18
+ ): ClipboardModule | null {
19
+ if (cachedClipboardModule !== undefined) {
20
+ return cachedClipboardModule;
21
+ }
22
+
23
+ if (environment.TERMUX_VERSION || !hasGraphicalSession(platform, environment)) {
24
+ cachedClipboardModule = null;
25
+ return cachedClipboardModule;
26
+ }
27
+
28
+ try {
29
+ cachedClipboardModule = require("@mariozechner/clipboard") as ClipboardModule;
30
+ } catch {
31
+ cachedClipboardModule = null;
32
+ }
33
+
34
+ return cachedClipboardModule;
35
+ }
36
+
37
+ export interface NativeModuleProviderOptions {
38
+ priority?: number;
39
+ moduleLoader?: ClipboardModuleLoader;
40
+ }
41
+
42
+ export class NativeModuleProvider implements ClipboardImageProvider {
43
+ readonly capabilities;
44
+ private readonly moduleLoader: ClipboardModuleLoader;
45
+
46
+ constructor(options: NativeModuleProviderOptions = {}) {
47
+ this.capabilities = {
48
+ id: "native-module",
49
+ name: "@mariozechner/clipboard",
50
+ platforms: "*" as const,
51
+ priority: options.priority ?? 100,
52
+ };
53
+ this.moduleLoader = options.moduleLoader ?? loadClipboardModule;
54
+ }
55
+
56
+ isAvailable(context: ClipboardProviderContext): boolean {
57
+ return this.moduleLoader(context.platform, context.environment) !== null;
58
+ }
59
+
60
+ async read(context: ClipboardProviderContext): Promise<ClipboardReadResult> {
61
+ const clipboard = this.moduleLoader(context.platform, context.environment);
62
+ if (!clipboard) {
63
+ return { available: false, image: null };
64
+ }
65
+
66
+ if (!clipboard.hasImage()) {
67
+ return { available: true, image: null };
68
+ }
69
+
70
+ const imageData = await clipboard.getImageBinary();
71
+ if (!imageData || imageData.length === 0) {
72
+ return { available: true, image: null };
73
+ }
74
+
75
+ const bytes = imageData instanceof Uint8Array ? imageData : Uint8Array.from(imageData);
76
+ return {
77
+ available: true,
78
+ image: {
79
+ bytes,
80
+ mimeType: "image/png",
81
+ },
82
+ };
83
+ }
84
+ }
@@ -0,0 +1,95 @@
1
+ import { runPowerShellCommand, type PowerShellCommandResult, type RunPowerShellCommandOptions } from "../powershell.js";
2
+ import { MAX_BUFFER_BYTES, READ_TIMEOUT_MS } from "./command-runner.js";
3
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
4
+
5
+ export type PowerShellRunner = (
6
+ script: string,
7
+ options: RunPowerShellCommandOptions,
8
+ ) => PowerShellCommandResult;
9
+
10
+ export interface PowerShellFormsProviderOptions {
11
+ priority?: number;
12
+ powerShellRunner?: PowerShellRunner;
13
+ }
14
+
15
+ const READ_IMAGE_SCRIPT = `
16
+ $ErrorActionPreference = 'Stop'
17
+ Add-Type -AssemblyName System.Windows.Forms
18
+ Add-Type -AssemblyName System.Drawing
19
+
20
+ if (-not [System.Windows.Forms.Clipboard]::ContainsImage()) {
21
+ return
22
+ }
23
+
24
+ $image = [System.Windows.Forms.Clipboard]::GetImage()
25
+ if ($null -eq $image) {
26
+ return
27
+ }
28
+
29
+ $stream = New-Object System.IO.MemoryStream
30
+ try {
31
+ $image.Save($stream, [System.Drawing.Imaging.ImageFormat]::Png)
32
+ [System.Convert]::ToBase64String($stream.ToArray())
33
+ } finally {
34
+ $stream.Dispose()
35
+ $image.Dispose()
36
+ }
37
+ `;
38
+
39
+ export class PowerShellFormsProvider implements ClipboardImageProvider {
40
+ readonly capabilities;
41
+ private readonly powerShellRunner: PowerShellRunner;
42
+
43
+ constructor(options: PowerShellFormsProviderOptions = {}) {
44
+ this.capabilities = {
45
+ id: "powershell-forms",
46
+ name: "Windows PowerShell Forms clipboard",
47
+ platforms: ["win32"],
48
+ priority: options.priority ?? 20,
49
+ };
50
+ this.powerShellRunner = options.powerShellRunner ?? runPowerShellCommand;
51
+ }
52
+
53
+ isAvailable(_context: ClipboardProviderContext): boolean {
54
+ return true;
55
+ }
56
+
57
+ read(_context: ClipboardProviderContext): ClipboardReadResult {
58
+ const result = this.powerShellRunner(READ_IMAGE_SCRIPT, {
59
+ encoded: true,
60
+ maxBuffer: MAX_BUFFER_BYTES,
61
+ sta: true,
62
+ timeout: READ_TIMEOUT_MS,
63
+ });
64
+
65
+ if (result.missingCommand) {
66
+ return { available: false, image: null };
67
+ }
68
+
69
+ if (!result.ok) {
70
+ return { available: true, image: null };
71
+ }
72
+
73
+ const base64 = result.stdout.trim();
74
+ if (!base64) {
75
+ return { available: true, image: null };
76
+ }
77
+
78
+ try {
79
+ const bytes = Buffer.from(base64, "base64");
80
+ if (bytes.length === 0) {
81
+ return { available: true, image: null };
82
+ }
83
+
84
+ return {
85
+ available: true,
86
+ image: {
87
+ bytes: new Uint8Array(bytes),
88
+ mimeType: "image/png",
89
+ },
90
+ };
91
+ } catch {
92
+ return { available: true, image: null };
93
+ }
94
+ }
95
+ }
@@ -0,0 +1,88 @@
1
+ import type {
2
+ ClipboardImageProvider,
3
+ ClipboardProviderContext,
4
+ ClipboardReadResult,
5
+ } from "./types.js";
6
+ import type { ClipboardImage } from "../types.js";
7
+
8
+ export interface ClipboardProviderAttempt {
9
+ providerId: string;
10
+ available: boolean;
11
+ imageFound: boolean;
12
+ skipped: boolean;
13
+ }
14
+
15
+ export interface ClipboardProviderRegistryReadResult {
16
+ image: ClipboardImage | null;
17
+ attempts: ClipboardProviderAttempt[];
18
+ }
19
+
20
+ function supportsPlatform(provider: ClipboardImageProvider, platform: NodeJS.Platform): boolean {
21
+ const { platforms } = provider.capabilities;
22
+ return platforms === "*" || platforms.includes(platform);
23
+ }
24
+
25
+ function byPriority(left: ClipboardImageProvider, right: ClipboardImageProvider): number {
26
+ return left.capabilities.priority - right.capabilities.priority;
27
+ }
28
+
29
+ export class ClipboardProviderRegistry {
30
+ private readonly providers: ClipboardImageProvider[] = [];
31
+
32
+ constructor(providers: readonly ClipboardImageProvider[] = []) {
33
+ for (const provider of providers) {
34
+ this.register(provider);
35
+ }
36
+ }
37
+
38
+ register(provider: ClipboardImageProvider): this {
39
+ this.providers.push(provider);
40
+ return this;
41
+ }
42
+
43
+ getProviders(platform: NodeJS.Platform): ClipboardImageProvider[] {
44
+ return this.providers.filter((provider) => supportsPlatform(provider, platform)).sort(byPriority);
45
+ }
46
+
47
+ async getEligible(context: ClipboardProviderContext): Promise<ClipboardImageProvider[]> {
48
+ const eligible: ClipboardImageProvider[] = [];
49
+ for (const provider of this.getProviders(context.platform)) {
50
+ if (await provider.isAvailable(context)) {
51
+ eligible.push(provider);
52
+ }
53
+ }
54
+
55
+ return eligible;
56
+ }
57
+
58
+ async read(context: ClipboardProviderContext): Promise<ClipboardProviderRegistryReadResult> {
59
+ const attempts: ClipboardProviderAttempt[] = [];
60
+
61
+ for (const provider of this.getProviders(context.platform)) {
62
+ const isAvailable = await provider.isAvailable(context);
63
+ if (!isAvailable) {
64
+ attempts.push({
65
+ providerId: provider.capabilities.id,
66
+ available: false,
67
+ imageFound: false,
68
+ skipped: true,
69
+ });
70
+ continue;
71
+ }
72
+
73
+ const result: ClipboardReadResult = await provider.read(context);
74
+ attempts.push({
75
+ providerId: provider.capabilities.id,
76
+ available: result.available,
77
+ imageFound: result.image !== null,
78
+ skipped: false,
79
+ });
80
+
81
+ if (result.image) {
82
+ return { image: result.image, attempts };
83
+ }
84
+ }
85
+
86
+ return { image: null, attempts };
87
+ }
88
+ }
@@ -0,0 +1,24 @@
1
+ import type { ClipboardImage } from "../types.js";
2
+
3
+ export interface ClipboardReadResult {
4
+ available: boolean;
5
+ image: ClipboardImage | null;
6
+ }
7
+
8
+ export interface ClipboardProviderContext {
9
+ platform: NodeJS.Platform;
10
+ environment: NodeJS.ProcessEnv;
11
+ }
12
+
13
+ export interface ProviderCapabilities {
14
+ readonly id: string;
15
+ readonly name: string;
16
+ readonly platforms: readonly NodeJS.Platform[] | "*";
17
+ readonly priority: number;
18
+ }
19
+
20
+ export interface ClipboardImageProvider {
21
+ readonly capabilities: ProviderCapabilities;
22
+ isAvailable(context: ClipboardProviderContext): boolean | Promise<boolean>;
23
+ read(context: ClipboardProviderContext): ClipboardReadResult | Promise<ClipboardReadResult>;
24
+ }
@@ -0,0 +1,81 @@
1
+ import { normalizeMimeType, selectPreferredImageMimeType } from "../image-mime.js";
2
+ import {
3
+ defaultCommandRunner,
4
+ LIST_TYPES_TIMEOUT_MS,
5
+ MAX_BUFFER_BYTES,
6
+ READ_TIMEOUT_MS,
7
+ type CommandRunner,
8
+ } from "./command-runner.js";
9
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
10
+
11
+ export interface WlPasteProviderOptions {
12
+ priority?: number;
13
+ commandRunner?: CommandRunner;
14
+ }
15
+
16
+ export class WlPasteProvider implements ClipboardImageProvider {
17
+ readonly capabilities;
18
+ private readonly commandRunner: CommandRunner;
19
+
20
+ constructor(options: WlPasteProviderOptions = {}) {
21
+ this.capabilities = {
22
+ id: "wl-paste",
23
+ name: "wl-paste",
24
+ platforms: ["linux"],
25
+ priority: options.priority ?? 10,
26
+ };
27
+ this.commandRunner = options.commandRunner ?? defaultCommandRunner;
28
+ }
29
+
30
+ isAvailable(_context: ClipboardProviderContext): boolean {
31
+ return true;
32
+ }
33
+
34
+ read(context: ClipboardProviderContext): ClipboardReadResult {
35
+ const listTypes = this.commandRunner("wl-paste", ["--list-types"], {
36
+ environment: context.environment,
37
+ maxBuffer: MAX_BUFFER_BYTES,
38
+ timeout: LIST_TYPES_TIMEOUT_MS,
39
+ });
40
+ if (listTypes.missingCommand) {
41
+ return { available: false, image: null };
42
+ }
43
+
44
+ if (!listTypes.ok) {
45
+ return { available: true, image: null };
46
+ }
47
+
48
+ const mimeTypes = listTypes.stdout
49
+ .toString("utf8")
50
+ .split(/\r?\n/)
51
+ .map((mimeType) => mimeType.trim())
52
+ .filter((mimeType) => mimeType.length > 0);
53
+
54
+ const selectedMimeType = selectPreferredImageMimeType(mimeTypes);
55
+ if (!selectedMimeType) {
56
+ return { available: true, image: null };
57
+ }
58
+
59
+ const imageData = this.commandRunner(
60
+ "wl-paste",
61
+ ["--type", selectedMimeType, "--no-newline"],
62
+ {
63
+ environment: context.environment,
64
+ maxBuffer: MAX_BUFFER_BYTES,
65
+ timeout: READ_TIMEOUT_MS,
66
+ },
67
+ );
68
+
69
+ if (!imageData.ok || imageData.stdout.length === 0) {
70
+ return { available: true, image: null };
71
+ }
72
+
73
+ return {
74
+ available: true,
75
+ image: {
76
+ bytes: new Uint8Array(imageData.stdout),
77
+ mimeType: normalizeMimeType(selectedMimeType),
78
+ },
79
+ };
80
+ }
81
+ }
@@ -0,0 +1,87 @@
1
+ import { normalizeMimeType, selectPreferredImageMimeType, SUPPORTED_IMAGE_MIME_TYPES } from "../image-mime.js";
2
+ import {
3
+ defaultCommandRunner,
4
+ LIST_TYPES_TIMEOUT_MS,
5
+ MAX_BUFFER_BYTES,
6
+ READ_TIMEOUT_MS,
7
+ type CommandRunner,
8
+ } from "./command-runner.js";
9
+ import type { ClipboardImageProvider, ClipboardProviderContext, ClipboardReadResult } from "./types.js";
10
+
11
+ export interface XclipProviderOptions {
12
+ priority?: number;
13
+ commandRunner?: CommandRunner;
14
+ }
15
+
16
+ export class XclipProvider implements ClipboardImageProvider {
17
+ readonly capabilities;
18
+ private readonly commandRunner: CommandRunner;
19
+
20
+ constructor(options: XclipProviderOptions = {}) {
21
+ this.capabilities = {
22
+ id: "xclip",
23
+ name: "xclip",
24
+ platforms: ["linux"],
25
+ priority: options.priority ?? 20,
26
+ };
27
+ this.commandRunner = options.commandRunner ?? defaultCommandRunner;
28
+ }
29
+
30
+ isAvailable(_context: ClipboardProviderContext): boolean {
31
+ return true;
32
+ }
33
+
34
+ read(context: ClipboardProviderContext): ClipboardReadResult {
35
+ const targets = this.commandRunner(
36
+ "xclip",
37
+ ["-selection", "clipboard", "-t", "TARGETS", "-o"],
38
+ {
39
+ environment: context.environment,
40
+ maxBuffer: MAX_BUFFER_BYTES,
41
+ timeout: LIST_TYPES_TIMEOUT_MS,
42
+ },
43
+ );
44
+
45
+ if (targets.missingCommand) {
46
+ return { available: false, image: null };
47
+ }
48
+
49
+ const advertisedMimeTypes = targets.ok
50
+ ? targets.stdout
51
+ .toString("utf8")
52
+ .split(/\r?\n/)
53
+ .map((mimeType) => mimeType.trim())
54
+ .filter((mimeType) => mimeType.length > 0)
55
+ : [];
56
+
57
+ const preferredMimeType =
58
+ advertisedMimeTypes.length > 0 ? selectPreferredImageMimeType(advertisedMimeTypes) : null;
59
+ const mimeTypesToTry = preferredMimeType
60
+ ? [preferredMimeType, ...SUPPORTED_IMAGE_MIME_TYPES]
61
+ : [...SUPPORTED_IMAGE_MIME_TYPES];
62
+
63
+ for (const mimeType of mimeTypesToTry) {
64
+ const imageData = this.commandRunner(
65
+ "xclip",
66
+ ["-selection", "clipboard", "-t", mimeType, "-o"],
67
+ {
68
+ environment: context.environment,
69
+ maxBuffer: MAX_BUFFER_BYTES,
70
+ timeout: READ_TIMEOUT_MS,
71
+ },
72
+ );
73
+
74
+ if (imageData.ok && imageData.stdout.length > 0) {
75
+ return {
76
+ available: true,
77
+ image: {
78
+ bytes: new Uint8Array(imageData.stdout),
79
+ mimeType: normalizeMimeType(mimeType),
80
+ },
81
+ };
82
+ }
83
+ }
84
+
85
+ return { available: true, image: null };
86
+ }
87
+ }
@@ -0,0 +1,75 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ export interface ShellCommandContext {
4
+ platform: NodeJS.Platform;
5
+ environment: NodeJS.ProcessEnv;
6
+ }
7
+
8
+ export type CommandExists = (command: string, context: ShellCommandContext) => boolean;
9
+
10
+ export interface WrappedCommand {
11
+ command: string;
12
+ args: string[];
13
+ wrapped: boolean;
14
+ }
15
+
16
+ const COMMAND_EXISTS_TIMEOUT_MS = 1000;
17
+ const COMMAND_EXISTS_MAX_BUFFER_BYTES = 1024 * 1024;
18
+ const commandExistsCache = new Map<string, boolean>();
19
+
20
+ export function hasGraphicalSession(platform: NodeJS.Platform, environment: NodeJS.ProcessEnv): boolean {
21
+ return platform !== "linux" || Boolean(environment.DISPLAY || environment.WAYLAND_DISPLAY);
22
+ }
23
+
24
+ export function isWaylandSession(environment: NodeJS.ProcessEnv): boolean {
25
+ return Boolean(environment.WAYLAND_DISPLAY) || environment.XDG_SESSION_TYPE === "wayland";
26
+ }
27
+
28
+ export function isTmuxSession(environment: NodeJS.ProcessEnv): boolean {
29
+ return Boolean(environment.TMUX);
30
+ }
31
+
32
+ export const defaultCommandExists: CommandExists = (command, context) => {
33
+ const lookupCommand = context.platform === "win32" ? "where" : "which";
34
+ const cacheKey = `${context.platform}:${lookupCommand}:${command}:${context.environment.PATH ?? ""}`;
35
+ const cached = commandExistsCache.get(cacheKey);
36
+ if (cached !== undefined) {
37
+ return cached;
38
+ }
39
+
40
+ const result = spawnSync(lookupCommand, [command], {
41
+ env: context.environment,
42
+ maxBuffer: COMMAND_EXISTS_MAX_BUFFER_BYTES,
43
+ stdio: ["ignore", "pipe", "pipe"],
44
+ timeout: COMMAND_EXISTS_TIMEOUT_MS,
45
+ windowsHide: true,
46
+ });
47
+ const exists = !result.error && result.status === 0;
48
+ commandExistsCache.set(cacheKey, exists);
49
+ return exists;
50
+ };
51
+
52
+ export function buildNamespaceWrappedCommand(
53
+ command: string,
54
+ args: readonly string[],
55
+ context: ShellCommandContext,
56
+ commandExists: CommandExists = defaultCommandExists,
57
+ ): WrappedCommand {
58
+ try {
59
+ if (
60
+ context.platform !== "darwin" ||
61
+ !isTmuxSession(context.environment) ||
62
+ !commandExists("reattach-to-user-namespace", context)
63
+ ) {
64
+ return { command, args: [...args], wrapped: false };
65
+ }
66
+
67
+ return {
68
+ command: "reattach-to-user-namespace",
69
+ args: [command, ...args],
70
+ wrapped: true,
71
+ };
72
+ } catch {
73
+ return { command, args: [...args], wrapped: false };
74
+ }
75
+ }